diff --git a/data/extension-node-map.json b/data/extension-node-map.json index 9038e2f..38d5f77 100644 --- a/data/extension-node-map.json +++ b/data/extension-node-map.json @@ -4778,6 +4778,7 @@ "CharacterInText", "ChatGPTOpenAI", "Color", + "Seed_", "CombineMasks_", "EnhanceImage", "GradientImage", diff --git a/web/javascript/app_mixlab.js b/web/javascript/app_mixlab.js index ec6a4c9..179a1fd 100644 --- a/web/javascript/app_mixlab.js +++ b/web/javascript/app_mixlab.js @@ -409,8 +409,8 @@ app.registerExtension({ } } - app.canvas.centerOnNode(node) - app.canvas.setZoom(0.45) + // app.canvas.centerOnNode(node) + // app.canvas.setZoom(0.45) } } }) diff --git a/web/javascript/smart_connect.js b/web/javascript/smart_connect.js new file mode 100644 index 0000000..1a72948 --- /dev/null +++ b/web/javascript/smart_connect.js @@ -0,0 +1,239 @@ +const smart_connect_config_input = [ + { + node_type: 'CLIPTextEncode', + node_widget_name: 'text', + inputNodeName: 'RandomPrompt', + inputNode_output_name: 'STRING' + }, + { + node_type: 'CLIPTextEncode', + node_widget_name: 'text', + inputNodeName: 'EmbeddingPrompt', + inputNode_output_name: 'STRING' + }, + { + node_type: 'CLIPTextEncode', + node_widget_name: 'text', + inputNodeName: 'ChinesePrompt_Mix', + inputNode_output_name: 'prompt' + } +] + +const smart_connect_config_output = [ + { + node_type: 'LoadImage', + node_output_name: 'IMAGE', + outputNodeName: 'ClipInterrogator', + outputNode_input_name: 'image' + } +] + +// import { +// convertToInput, +// getConfig, +// isConvertableWidget +// } from '../../../extensions/core/widgetInputs.js' + +const CONVERTED_TYPE = 'converted-widget' +const GET_CONFIG = Symbol() + +function getConfig (widgetName) { + const { nodeData } = this.constructor + return ( + nodeData?.input?.required[widgetName] ?? + nodeData?.input?.optional?.[widgetName] + ) +} + +function hideWidget (node, widget, suffix = '') { + widget.origType = widget.type + widget.origComputeSize = widget.computeSize + widget.origSerializeValue = widget.serializeValue + widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically + widget.type = CONVERTED_TYPE + suffix + widget.serializeValue = () => { + // Prevent serializing the widget if we have no input linked + if (!node.inputs) { + return undefined + } + let node_input = node.inputs.find(i => i.widget?.name === widget.name) + + if (!node_input || !node_input.link) { + return undefined + } + return widget.origSerializeValue + ? widget.origSerializeValue() + : widget.value + } + + // Hide any linked widgets, e.g. seed+seedControl + if (widget.linkedWidgets) { + for (const w of widget.linkedWidgets) { + hideWidget(node, w, ':' + widget.name) + } + } +} + +function convertToInput (node, widget, config) { + hideWidget(node, widget) + + const type = config[0] + + // Add input and store widget config for creating on primitive node + const sz = node.size + node.addInput(widget.name, type, { + widget: { name: widget.name, [GET_CONFIG]: () => config } + }) + + for (const widget of node.widgets) { + widget.last_y += LiteGraph.NODE_SLOT_HEIGHT + } + + // Restore original size but grow if needed + node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]) +} + +export function smart_init () { + LGraphCanvas.prototype._createNodeForInput = function ( + node, + widget, + inputNodeName, + inputNode_slot + ) { + // console.log(node.pos) + + // var widget = node.widgets.filter(w => w.name === node_widget_name)[0] + if (widget) { + // 如果有存在的,没有连线输出的,自动连,不新建 + let input_node = null + + Array.from(app.graph.findNodesByType(inputNodeName), n => { + var links = n.outputs.filter(o => o.name === inputNode_slot)[0].links + // console.log(links) + if (!links || links?.length === 0) input_node = n + }) + // 新建 + if (!input_node) { + input_node = LiteGraph.createNode(inputNodeName) + input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48] + app.canvas.graph.add(input_node, false) + } else { + input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48] + } + + const config = getConfig.call(node, widget.name) ?? [ + widget.type, + widget.options || {} + ] + let node_slotType = config[0] + // 如果input没有,则创建 + if (!node.inputs.filter(inp => inp.name === widget.name)[0]) + convertToInput(node, widget, config) + input_node.connectByType(inputNode_slot, node, node_slotType) + } + } + + LGraphCanvas.prototype._createNodeForOutput = function ( + node, + widget, + outputNodeName, + outputNode_slot + ) { + if (widget) { + let output_node + Array.from(app.graph.findNodesByType(outputNodeName), n => { + var links = n.inputs.filter(o => o.name === outputNode_slot)[0].links + // console.log(links) + if (!links || links?.length === 0) output_node = n + }) + console.log('output_node', output_node) + + if (!output_node) { + // 新建 + output_node = LiteGraph.createNode(outputNodeName) + output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48] + app.canvas.graph.add(output_node, false) + } else { + output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48] + } + + console.log(output_node) + const config = getConfig.call(node, widget.name) ?? [ + widget.type, + widget.options || {} + ] + let node_slotType = config[0] + + node.connectByType(node_slotType, output_node, outputNode_slot) + } + } +} + +export function addSmartMenu (options, node) { + let sopts = [] + + for (const sc of smart_connect_config_input) { + // 有智能推荐,则出现 + if (node.type === sc.node_type) { + // 则出现 randomPrompt + // CLIPTextEncode 的widget ,name== 'text' + let node_widget_name = sc.node_widget_name + const widget = node.widgets.filter(w => w.name === node_widget_name)[0] + + let isLinkNull = true + // 如果input里已经有,但是link为空 + if (node.inputs.filter(inp => inp.name === node_widget_name)[0]) { + isLinkNull = + node.inputs.filter(inp => inp.name === node_widget_name)[0].link === + null + } + + if (widget && isLinkNull) { + sopts.push({ + content: sc.inputNodeName.split('_')[0], + callback: () => { + LGraphCanvas.prototype._createNodeForInput( + node, //当前node + widget, //当前node里需要自动连线的widget + sc.inputNodeName, //作为input的node type + sc.inputNode_output_name // 作为input的node的outputs的name. the input slot type of the target node + ) + } + }) + } + } + } + + for (const sc of smart_connect_config_output) { + if (node.type === sc.node_type) { + let node_output_name = sc.node_output_name + const widget = node.outputs.filter(w => w.name === node_output_name)[0] + + let isLinkNull = true + // 如果output里 link为空 + if (node.outputs.filter(inp => inp.name === node_output_name)[0]) { + isLinkNull = + node.outputs.filter(inp => inp.name === node_output_name)[0].links + .length === 0 + } + + if (widget && isLinkNull) { + sopts.push({ + content: sc.outputNodeName.split('_')[0], + callback: () => { + LGraphCanvas.prototype._createNodeForOutput( + node, //当前node + widget, //当前node里需要自动连线的widget + sc.outputNodeName, //作为input的node type + sc.outputNode_input_name // 作为input的node的outputs的name. the input slot type of the target node + ) + } + }) + } + } + } + + if (sopts.length > 0) options = [...sopts, null, ...options] + + return options +} diff --git a/web/javascript/ui_mixlab.js b/web/javascript/ui_mixlab.js index f264ee4..ccf7147 100644 --- a/web/javascript/ui_mixlab.js +++ b/web/javascript/ui_mixlab.js @@ -6,6 +6,8 @@ import { GroupNodeHandler } from '../../../extensions/core/groupNode.js' +import { smart_init, addSmartMenu } from './smart_connect.js' + function copyNodeValues (src, dest) { // title dest.title = src.title @@ -639,13 +641,16 @@ app.registerExtension({ app.canvas.graph.remove(node) } + smart_init() + const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions // store the existing method LGraphCanvas.prototype.getNodeMenuOptions = function (node) { // replace it const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one node.setDirtyCanvas(true, true) // force a redraw of (foreground, background) + console.log('getNodeMenuOptions', node.type == 'CLIPTextEncode') - return [ + let opts = [ { content: 'Help ♾️Mixlab', // with a name callback: () => { @@ -653,14 +658,46 @@ app.registerExtension({ } // and the callback }, { - content: 'Fix node v2♾️Mixlab', // with a name + content: 'Fix node v2', // with a name callback: () => { LGraphCanvas.prototype.fixTheNode(node) } - }, - null, - ...options - ] // and return the options + } + ] + + opts = addSmartMenu(opts,node) + + // if (node.type == 'CLIPTextEncode') { + // // 则出现 randomPrompt + // // CLIPTextEncode 的widget ,name== 'text' + // let node_widget_name = 'text' + // const widget = node.widgets.filter(w => w.name === node_widget_name)[0] + + // let mixlab_nodes_smart_connect= [{node_type:'CLIPTextEncode', + // node_widget_name:'text', + // inputNodeName:'RandomPrompt', + // inputNode_output_type:'STRING'}] + + // if (widget) { + // opts = [ + // { + // content: 'RandomPrompt', + // callback: () => { + // LGraphCanvas.prototype._createNodeForInput( + // node, //当前node + // widget,//当前node里需要自动连线的widget + // 'RandomPrompt',//作为input的node type + // 'STRING'// 作为input的node的outputs的type. the input slot type of the target node + // ) + // } + // }, + // null, + // ...opts + // ] + // } + // } + + return [...opts, null, ...options] // and return the options } const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions // store the existing method @@ -1136,5 +1173,19 @@ app.registerExtension({ return options } }, 1000) + }, + async loadedGraphNode (node, app) { + // console.log( + // '#ui init', + // app.graph._nodes[app.graph._nodes.length - 1].id, + // node.id + // ) + try { + // 用来居中显示节点 + if ((app.graph._nodes[app.graph._nodes.length - 1].id, node.id)) { + app.canvas.centerOnNode(node) + app.canvas.setZoom(0.45) + } + } catch (error) {} } })