import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; import { MultiButtonWidget } from "./multi_button_widget.js"; // 获取input目录的文件列表 async function getInputFileList() { try { const response = await fetch('/object_info'); const data = await response.json(); // 从 LoadImage 节点类型获取可用文件列表 const loadImageInfo = data.LoadImage; if (loadImageInfo && loadImageInfo.input && loadImageInfo.input.required && loadImageInfo.input.required.image) { return loadImageInfo.input.required.image[0]; // 返回文件列表数组 } return []; } catch (error) { console.error("获取文件列表失败:", error); return []; } } // 删除图片文件(直接删除,无确认弹窗) async function deleteImageFile(filename) { try { const response = await api.fetchApi('/lg/delete_image', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: filename }) }); if (response.status === 200) { const result = await response.json(); if (result.success) { console.log(`文件 ${filename} 删除成功`); return true; } } else { const error = await response.json(); console.error(`删除失败: ${error.error || '未知错误'}`); return false; } } catch (error) { console.error(`删除文件失败: ${error}`); return false; } } // 加载最新图片 - 参考PB.js的实现方式 async function loadLatestImage(node, folder_type) { try { // 获取指定目录中的最新图片 const res = await api.fetchApi(`/lg/get/latest_image?type=${folder_type}`, { cache: "no-store" }); if (res.status === 200) { const item = await res.json(); if (item && item.filename) { // 找到图像小部件 const imageWidget = node.widgets.find(w => w.name === 'image'); if (!imageWidget) return false; // 直接使用原始文件,不需要复制到input const displayValue = `${item.filename} [${folder_type}]`; imageWidget.value = displayValue; // 通过回调更新预览图像 if (typeof imageWidget.callback === "function") { imageWidget.callback(displayValue); } // 更新画布 app.graph.setDirtyCanvas(true); return true; } } } catch (error) { console.error(`加载图像失败: ${error}`); } return false; } // 扩展ContextMenu以支持图片缩略图和删除功能 function extendContextMenuForThumbnails() { const originalContextMenu = LiteGraph.ContextMenu; LiteGraph.ContextMenu = function(values, options) { const ctx = originalContextMenu.call(this, values, options); // 检查是否是LG_LoadImage节点的image widget的下拉菜单 if (options?.className === 'dark' && values?.length > 0) { // 等待DOM更新后处理 requestAnimationFrame(() => { const currentNode = LGraphCanvas.active_canvas?.current_node; // 检查是否是LG_LoadImage节点的image widget if (currentNode?.comfyClass === "LG_LoadImage") { const imageWidget = currentNode.widgets?.find(w => w.name === 'image'); if (imageWidget && imageWidget.options?.values?.length === values.length) { // 限制菜单宽度 - 调整为更宽 ctx.root.style.maxWidth = '400px'; ctx.root.style.minWidth = '350px'; // 为每个菜单项添加缩略图和删除按钮 const menuItems = ctx.root.querySelectorAll('.litemenu-entry'); menuItems.forEach((item, index) => { if (index < values.length) { const filename = values[index]; addThumbnailAndDeleteToMenuItem(item, filename, currentNode, ctx); } }); } } }); } return ctx; }; // 保持原型链 LiteGraph.ContextMenu.prototype = originalContextMenu.prototype; } // 为菜单项添加缩略图和删除按钮 function addThumbnailAndDeleteToMenuItem(menuItem, filename, node, contextMenu) { // 避免重复添加 if (menuItem.querySelector('.thumbnail-container')) { return; } // 保存原始文本内容 const originalText = menuItem.textContent; // 清空菜单项内容 menuItem.innerHTML = ''; // 设置菜单项样式为flex布局 menuItem.style.cssText += ` display: flex; align-items: center; padding: 6px 12px; min-height: 48px; position: relative; `; // 创建缩略图容器 const thumbnailContainer = document.createElement('div'); thumbnailContainer.className = 'thumbnail-container'; thumbnailContainer.style.cssText = ` width: 40px; height: 40px; margin-right: 10px; border-radius: 4px; overflow: hidden; background: #222; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #444; `; // 创建缩略图 const thumbnail = document.createElement('img'); thumbnail.style.cssText = ` max-width: 100%; max-height: 100%; object-fit: cover; `; // 设置图片源 thumbnail.src = `/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=`; thumbnail.alt = filename; // 处理图片加载失败 thumbnail.onerror = function() { thumbnailContainer.innerHTML = ` 无
预览
`; }; thumbnailContainer.appendChild(thumbnail); // 创建文件名标签 const textLabel = document.createElement('span'); // 截断长文件名 - 保留前10位和后10位文件名及扩展名 let displayName = originalText; if (displayName.length > 35) { // 保留文件扩展名 const lastDotIndex = displayName.lastIndexOf('.'); if (lastDotIndex > 0) { const name = displayName.substring(0, lastDotIndex); const extension = displayName.substring(lastDotIndex); if (name.length > 20) { // 保留前10位 + ... + 后10位 + 扩展名 const firstPart = name.substring(0, 10); const lastPart = name.substring(name.length - 10); displayName = firstPart + '...' + lastPart + extension; } } else { // 没有扩展名的情况,保留前10位和后10位 if (displayName.length > 20) { const firstPart = displayName.substring(0, 10); const lastPart = displayName.substring(displayName.length - 10); displayName = firstPart + '...' + lastPart; } } } textLabel.textContent = displayName; textLabel.title = originalText; // 悬停时显示完整文件名 textLabel.style.cssText = ` color: inherit; font-size: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; cursor: pointer; max-width: 280px; min-width: 0; `; // 创建删除按钮 - 扩大点击范围 const deleteButton = document.createElement('button'); deleteButton.innerHTML = '✕'; deleteButton.title = `删除 ${filename}`; deleteButton.style.cssText = ` width: 28px; height: 28px; border: none; background: transparent; color: #888; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin-left: 8px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; opacity: 0.7; transition: all 0.15s; padding: 0; `; // 删除按钮悬停效果 - 更明显的反馈 deleteButton.addEventListener('mouseenter', () => { deleteButton.style.opacity = '1'; deleteButton.style.color = '#fff'; deleteButton.style.background = 'rgba(255, 255, 255, 0.15)'; deleteButton.style.transform = 'scale(1.05)'; }); deleteButton.addEventListener('mouseleave', () => { deleteButton.style.opacity = '0.7'; deleteButton.style.color = '#888'; deleteButton.style.background = 'transparent'; deleteButton.style.transform = 'scale(1)'; }); // 删除按钮点击事件 - 快速删除,无动画延迟 deleteButton.addEventListener('click', async (e) => { e.stopPropagation(); // 阻止触发菜单项选择 e.preventDefault(); // 立即显示删除中状态 deleteButton.innerHTML = '⋯'; deleteButton.style.pointerEvents = 'none'; deleteButton.style.opacity = '0.5'; // 直接执行删除操作,无确认弹窗 const deleted = await deleteImageFile(filename); if (deleted) { // 立即移除菜单项,无动画延迟 if (menuItem.parentNode) { menuItem.parentNode.removeChild(menuItem); } // 更新节点的文件列表 const imageWidget = node.widgets.find(w => w.name === 'image'); if (imageWidget) { // 重新获取文件列表 const fileList = await getInputFileList(); imageWidget.options.values = fileList; // 如果删除的是当前选中的文件,选择第一个可用文件 if (imageWidget.value === filename) { imageWidget.value = fileList.length > 0 ? fileList[0] : ''; // 触发回调更新预览 if (typeof imageWidget.callback === "function") { imageWidget.callback(imageWidget.value); } } // 更新画布 app.graph.setDirtyCanvas(true); } // 检查是否还有剩余菜单项,如果没有则关闭菜单 const remainingItems = contextMenu.root.querySelectorAll('.litemenu-entry'); if (remainingItems.length === 0) { contextMenu.close(); } } else { // 删除失败,恢复按钮状态 deleteButton.innerHTML = '✕'; deleteButton.style.pointerEvents = 'auto'; deleteButton.style.opacity = '0.7'; } }); // 创建可点击区域(除了删除按钮) const clickableArea = document.createElement('div'); clickableArea.style.cssText = ` display: flex; align-items: center; flex: 1; cursor: pointer; `; clickableArea.appendChild(thumbnailContainer); clickableArea.appendChild(textLabel); // 为可点击区域添加选择事件 clickableArea.addEventListener('click', () => { // 模拟原始菜单项点击 const imageWidget = node.widgets.find(w => w.name === 'image'); if (imageWidget) { imageWidget.value = filename; // 触发回调 if (typeof imageWidget.callback === "function") { imageWidget.callback(filename); } // 更新画布 app.graph.setDirtyCanvas(true); } // 关闭菜单 contextMenu.close(); }); // 组装菜单项 menuItem.appendChild(clickableArea); menuItem.appendChild(deleteButton); // 移除原有的点击事件,因为我们现在有自定义的点击处理 menuItem.onclick = null; } app.registerExtension({ name: "Comfy.LG.LoadImageButtons", init() { // 扩展ContextMenu以支持缩略图和删除功能 extendContextMenuForThumbnails(); }, async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name !== "LG_LoadImage") return; const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function() { const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; // 使用多按钮组件创建刷新按钮 const refreshWidget = this.addCustomWidget(MultiButtonWidget(app, "Refresh From", { labelWidth: 80, buttonSpacing: 4 }, [ { text: "Temp", callback: () => { loadLatestImage(this, "temp").then(success => { if (success) { app.graph.setDirtyCanvas(true); } }); } }, { text: "Output", callback: () => { loadLatestImage(this, "output").then(success => { if (success) { app.graph.setDirtyCanvas(true); } }); } } ])); refreshWidget.serialize = false; return result; }; } }); app.registerExtension({ name: "Comfy.LG.LoadImage_V2", init() { // 扩展ContextMenu以支持缩略图和删除功能 extendContextMenuForThumbnails(); }, async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name !== "LG_LoadImage_V2") return; // 保持节点的原始行为,不添加额外的前端功能 // auto_refresh的逻辑现在完全在后端处理 } });