From 92f9529fe354e6ebf36078f1e7de1107a2c5d08d Mon Sep 17 00:00:00 2001 From: matan Date: Fri, 8 Dec 2023 21:32:23 +0200 Subject: [PATCH] working demo --- __init__.py | 25 ++++ __pycache__/__init__.cpython-311.pyc | Bin 0 -> 1971 bytes __pycache__/__init__.cpython-39.pyc | Bin 0 -> 873 bytes javascript/learn-menu.js | 182 +++++++++++++++++++++++++++ javascript/learn.js | 45 +++++++ javascript/youtube.js | 115 +++++++++++++++++ 6 files changed, 367 insertions(+) create mode 100644 __init__.py create mode 100644 __pycache__/__init__.cpython-311.pyc create mode 100644 __pycache__/__init__.cpython-39.pyc create mode 100644 javascript/learn-menu.js create mode 100644 javascript/learn.js create mode 100644 javascript/youtube.js diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e6be964 --- /dev/null +++ b/__init__.py @@ -0,0 +1,25 @@ +import os +import filecmp +import shutil +import __main__ + +### The following was taken from https://github.com/FizzleDorf/ComfyUI_FizzNodes/blob/main/__init__.py +extentions_folder = os.path.join(os.path.dirname(os.path.realpath(__main__.__file__)), + "web" + os.sep + "extensions" + os.sep + "ComfyUI-Assistant") +javascript_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "javascript") + +if not os.path.exists(extentions_folder): + os.makedirs(extentions_folder) + +result = filecmp.dircmp(javascript_folder, extentions_folder) + +if result.left_only or result.diff_files: + file_list = list(result.left_only) + file_list.extend(x for x in result.diff_files if x not in file_list) + + for file in file_list: + src_file = os.path.join(javascript_folder, file) + dst_file = os.path.join(extentions_folder, file) + if os.path.exists(dst_file): + os.remove(dst_file) + shutil.copy(src_file, dst_file) diff --git a/__pycache__/__init__.cpython-311.pyc b/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb162acb395a43410344591af603431391aefb78 GIT binary patch literal 1971 zcmcIkPiWg#7=O=JY{`xr$A8-$nmA$k!@7EgjrNcRk`>}Cp{%6o!6gXKdS2w&meEtv zq-4d6z`_X3ltOaM#z?nyupD+B^svKjlZv3?odUbGH<#>`(>}>jYNajgviJ19?|uJ& z?~|S$MIs>tqvyFwOg}<@+k!iSCz~S`EIUX-60V{`hvNcvWM9GOaDTy1P=#|t_j=r2 zH}`l1x-IVBCGYTOO zj{om0yce@ql6uad3VTrXx@fyO33+qSsLwue#PvCA#|Z4Wa_-a~1`ls9`bb|T>e^`# zq<&XYe}?E-x7}O45GV1ygzMeSrNAcY&U78^gZc5Pi#HEOBz_$&trU`;4!rrjgh}$u z2~z(dD)d@Z+7tEWZl}FC)lRq1yT>4B>`u3Rl?$i#6nJ>LjnTFy;RU#93urv}!gjpD z{CCM`EJW5cQm3*~r}z3lR%+$-#o70#D3z&JtZOV(DXtc2NtGK~>GVcGG~8S?4D09; zkU50m9&ezfzAy0x)uTlG& z3=SU8XBR0^X|`4b0NGiI6hBp!3Mpwcdjc<8TA`X!6YGjZPL;1PXGKx2%bF-&Zmb^* z6Q7ehS!<}1Y8djzU{M?LbC^w}C-U&@<_>QDF6$r7z|*?f;V0CwAAvs_e~0-L%7QlV zQmw%Pba_RStBe=LT2ZcxBI6oGZJBWuMXs};B&+pejWAv%#j0ZfN9WKY^Hb7b{Z8LC zyYFIIsY*m;{jYlMssdyUdPEstD}F&BCuISkPJNZom8!;sDk*ECQm?MFkR+GOP7%sD zySJ+Sr(=)<-mt_tyFBBmT5^0CFHxCb8|*!-ptJZSv3-O%)}i#CMay){rdh__x04M5gapvV{L@- zu*LN?r#I*HXvW}%Om0Z$h7N`FR(kuQetukkZ^96+nZmVZ;LtCa{&*V+c&xQ*rXd)1 zRy5J#TDQ&QyGC@xjE*#CEiUqmOFZQgoo{O|U=HSBYb5d}P%dwBd7aB2MlZBcPY)yk zvc*~P)E!_sj zB$JS_=yHb$oX;cfb}#afjr{qF!-m5qdZSTn3BAc(*7BGZ)8&}EZ(FSgeZt2g2oll@b+^qczh_-NRep@zV^8>462ke5n zpvJM?=L9!^5&+lTFY!2+~tArPhCUseG_!#ad7KtPSig$Rsr5%?TG*ycRT1ZC1BG$s0(8;6L0C^?6?L3&rWQ^N z%S@t*I@e+?k8A^{^ki*=Jbr^zQ`-ii<5#H1nX#=5henh|HnX8jheJoAtzS!}d}_R| zL0=@TU(-i{R*Ab41yUPl*@nWroS^lSvYI(jZ$MDj2=?+)jx*e^KT+Q&2*6Em2fBZK Vevhv~$k%uqLg?}U+BE3|{sABH^F;sv literal 0 HcmV?d00001 diff --git a/javascript/learn-menu.js b/javascript/learn-menu.js new file mode 100644 index 0000000..5ebd6cd --- /dev/null +++ b/javascript/learn-menu.js @@ -0,0 +1,182 @@ +import {ComfyDialog} from "../../scripts/ui.js"; +import {startVideo} from "./youtube.js"; +import { app } from "../../scripts/app.js"; + + +const docStyle = document.createElement('style'); +docStyle.innerHTML = ` + .learn-menu { + display: flex; + justify-content: space-between; + width: 60vw; + color: white; + + } + + .category-list { + flex: 0 0 10%; + border-right: 1px solid #ccc; + padding-right: 1em; + display: flex; + flex-direction: column; + } + .category-item{ + border: 1px solid #ccc; + padding: 1em; + } + + .module-list { + flex: 1; + padding-left: 1em; + overflow-y: auto; + } + + .module-item { + border: 1px solid #ccc; + padding: 1em; + margin-bottom: 1em; + display: flex; + justify-content: space-around; + align-items: center; + } + + .module-item img { + height: 100%; + width: auto; + } + .module-item-details{ + display: flex; + flex-direction: column; + flex-grow: 1; + padding-left: 1em; + padding-right: 1em; + } + + .module-item button { + background: #007BFF; + border: none; + color: white; + padding: 10px 20px; + text-align: right; + text-decoration: none; + justify-content: right; + display: inline-block; + font-size: 16px; + margin: 10px 2px; + cursor: pointer; + } + +`; + +document.head.appendChild(docStyle); + +export const learnMenu = document.createElement('div'); +learnMenu.classList.add("learn-menu") + + +const tutorials = [ + { + title:"How to get started with ComfyUI", + description:`How and why to get started with ComfyUI. Join to OpenArt Contest with a Price Pool of over $13000 USD https://contest.openart.ai/#participate +This ComfyUi Starter Guide shows you how to install everything and get started. Learn ComfyUI with easy Workflow examples. Build and Sell powerful Workflows in no time.`, + videoId:`5dBAIhx28ow`, + categories:["All"], + initialWorkflow:`{ + "last_node_id": 9, + "last_link_id": 9, + "nodes": [], + "links": [], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 + }` + }, { + title:"How to use IP-Adapter", + description:`In this tutorial we will learn the how to use one of the most powerful tools in stable diffusion - the IP-Adapter`, + videoId:`yrqT5FaBoIU`, + categories:["All"], + initialWorkflow:`{ + "last_node_id": 9, + "last_link_id": 9, + "nodes": [], + "links": [], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 + }`, + keyframesWorkflows: { + 5: `{"last_node_id": 9,"last_link_id": 9,"nodes": [{"id": 7,"type": "CLIPTextEncode","pos": [413,389],"size": {"0": 425.27801513671875,"1": 180.6060791015625},"flags": {},"order": 3,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 5}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["text, watermark"]},{"id": 6,"type": "CLIPTextEncode","pos": [415,186],"size": {"0": 422.84503173828125,"1": 164.31304931640625},"flags": {},"order": 2,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 3}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"]},{"id": 5,"type": "EmptyLatentImage","pos": [473,609],"size": {"0": 315,"1": 106},"flags": {},"order": 0,"mode": 0,"outputs": [{"name": "LATENT","type": "LATENT","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "EmptyLatentImage"},"widgets_values": [512,512,1]},{"id": 4,"type": "CheckpointLoaderSimple","pos": [26,474],"size": {"0": 315,"1": 98},"flags": {},"order": 1,"mode": 0,"outputs": [{"name": "MODEL","type": "MODEL","links": [],"slot_index": 0},{"name": "CLIP","type": "CLIP","links": [3,5],"slot_index": 1},{"name": "VAE","type": "VAE","links": [],"slot_index": 2}],"properties": {"Node name for S&R": "CheckpointLoaderSimple"},"widgets_values": ["v1-5-pruned-emaonly.ckpt"]}],"links": [[3,4,1,6,0,"CLIP"],[5,4,1,7,0,"CLIP"]],"groups": [],"config": {},"extra": {},"version": 0.4}`, + 10:`{"last_node_id": 9,"last_link_id": 9,"nodes": [{"id": 7,"type": "CLIPTextEncode","pos": [413,389],"size": {"0": 425.27801513671875,"1": 180.6060791015625},"flags": {},"order": 3,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 5}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [6],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["text, watermark"]},{"id": 6,"type": "CLIPTextEncode","pos": [415,186],"size": {"0": 422.84503173828125,"1": 164.31304931640625},"flags": {},"order": 2,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 3}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [4],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"]},{"id": 5,"type": "EmptyLatentImage","pos": [473,609],"size": {"0": 315,"1": 106},"flags": {},"order": 0,"mode": 0,"outputs": [{"name": "LATENT","type": "LATENT","links": [2],"slot_index": 0}],"properties": {"Node name for S&R": "EmptyLatentImage"},"widgets_values": [512,512,1]},{"id": 3,"type": "KSampler","pos": [863,186],"size": {"0": 315,"1": 262},"flags": {},"order": 4,"mode": 0,"inputs": [{"name": "model","type": "MODEL","link": 1},{"name": "positive","type": "CONDITIONING","link": 4},{"name": "negative","type": "CONDITIONING","link": 6},{"name": "latent_image","type": "LATENT","link": 2}],"outputs": [{"name": "LATENT","type": "LATENT","links": [7],"slot_index": 0}],"properties": {"Node name for S&R": "KSampler"},"widgets_values": [156680208700286,"randomize",20,8,"euler","normal",1]},{"id": 8,"type": "VAEDecode","pos": [1209,188],"size": {"0": 210,"1": 46},"flags": {},"order": 5,"mode": 0,"inputs": [{"name": "samples","type": "LATENT","link": 7},{"name": "vae","type": "VAE","link": 8}],"outputs": [{"name": "IMAGE","type": "IMAGE","links": [9],"slot_index": 0}],"properties": {"Node name for S&R": "VAEDecode"}},{"id": 9,"type": "SaveImage","pos": [1451,189],"size": {"0": 210,"1": 58},"flags": {},"order": 6,"mode": 0,"inputs": [{"name": "images","type": "IMAGE","link": 9}],"properties": {},"widgets_values": ["ComfyUI"]},{"id": 4,"type": "CheckpointLoaderSimple","pos": [26,474],"size": {"0": 315,"1": 98},"flags": {},"order": 1,"mode": 0,"outputs": [{"name": "MODEL","type": "MODEL","links": [1],"slot_index": 0},{"name": "CLIP","type": "CLIP","links": [3,5],"slot_index": 1},{"name": "VAE","type": "VAE","links": [8],"slot_index": 2}],"properties": {"Node name for S&R": "CheckpointLoaderSimple"},"widgets_values": ["v1-5-pruned-emaonly.ckpt"]}],"links": [[1,4,0,3,0,"MODEL"],[2,5,0,3,3,"LATENT"],[3,4,1,6,0,"CLIP"],[4,6,0,3,1,"CONDITIONING"],[5,4,1,7,0,"CLIP"],[6,7,0,3,2,"CONDITIONING"],[7,3,0,8,0,"LATENT"],[8,4,2,8,1,"VAE"],[9,8,0,9,0,"IMAGE"]],"groups": [],"config": {},"extra": {},"version": 0.4}` + } + } + +] +const categories = ["All"] + + +const categoriesEl = document.createElement('div') +categoriesEl.classList.add('category-list') +categoriesEl.innerHTML = `

Categories

` +learnMenu.appendChild(categoriesEl) +const moduleListEl = document.createElement('div') +moduleListEl.classList.add('module-list') +learnMenu.appendChild(moduleListEl) + +for (let category of categories) { + const cat = document.createElement('li'); + cat.classList.add('category-item'); + cat.textContent = category; + cat.onclick = ()=>loadCategory(category) + categoriesEl.appendChild(cat); +} + +function loadCategory(category){ + moduleListEl.innerHTML = ""; + for (let tutorial of tutorials) { + if (tutorial.categories.includes(category) || category==="All") { + const moduleItem = document.createElement('div'); + moduleItem.classList.add('module-item'); + moduleItem.innerHTML = ` + module image +
+

${tutorial.title}

+

${tutorial.description}

+
+ + `; + moduleListEl.appendChild(moduleItem); + moduleItem.querySelector('button').addEventListener('click', () => { + LearnModal.get().close() + if(tutorial.initialWorkflow){ + app.loadGraphData(JSON.parse(tutorial.initialWorkflow)) + } + const keyframesFunctions = {} + if(tutorial.keyframesWorkflows){ + for (let keyframe in tutorial.keyframesWorkflows) { + const parsedWorkflow = JSON.parse(tutorial.keyframesWorkflows[keyframe]) + keyframesFunctions[keyframe] = ()=> app.loadGraphData(parsedWorkflow); + } + } + startVideo(tutorial.title,tutorial.videoId,keyframesFunctions) + }); + } + } +} + +loadCategory("All") + +export class LearnModal extends ComfyDialog{ + static instance=null + show(){ + super.show(learnMenu); + } + + static get(){ + if (LearnModal.instance === null) { + LearnModal.instance = new LearnModal(); + } + return LearnModal.instance; + } +} \ No newline at end of file diff --git a/javascript/learn.js b/javascript/learn.js new file mode 100644 index 0000000..0e6fe51 --- /dev/null +++ b/javascript/learn.js @@ -0,0 +1,45 @@ +import { app } from "../../scripts/app.js"; +import {LearnModal} from './learn-menu.js' + + +var docStyle = document.createElement('style'); +docStyle.innerHTML = ` +@keyframes Gradient { + 0%{background-position: 0% 50%} + 50%{background-position: 100% 50%} + 100%{background-position: 0% 50%} + } +`; + +document.head.appendChild(docStyle); + +let learnModalInstance = null; + + + +const ext = { + // Unique name for the extension + name: "ComfyUI-Assistant", + async setup(app) { + const menuLoadDefault = document.querySelector("#comfy-load-default-button"); + const learnBtn = document.createElement("button"); + learnBtn.textContent = "Learn"; + + if(!localStorage.getItem("clickedLearn")) { + learnBtn.style.background = "linear-gradient(270deg, #d53369, #daae51)"; + learnBtn.style.backgroundSize = "200% 200%"; + learnBtn.style.animation = "Gradient 2s ease infinite"; + learnBtn.addEventListener("click", ()=>{ + localStorage.setItem("clickedLearn","true") + learnBtn.style=null; + }) + } + learnBtn.addEventListener("click", ()=>{ + LearnModal.get().show(); + }) + menuLoadDefault.after(learnBtn); + }, +}; + +app.registerExtension(ext); + diff --git a/javascript/youtube.js b/javascript/youtube.js new file mode 100644 index 0000000..a8d8f0e --- /dev/null +++ b/javascript/youtube.js @@ -0,0 +1,115 @@ + + +const tag = document.createElement('script'); + +tag.src = "https://www.youtube.com/iframe_api"; +const firstScriptTag = document.getElementsByTagName('script')[0]; +firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + +let player; +const setVideo = (videoId)=> { + console.log(videoId) + player = new YT.Player('player',{ + videoId: videoId, + events: { + 'onStateChange': onPlayerStateChange + } + }); +} + + +var done = false; +var lastInterval = null; +let keyframesFunctions= {} +function onPlayerStateChange(event) { + if (event.data == YT.PlayerState.PLAYING) { + lastInterval = setInterval(function () { + // keyframesFunctions contains seconds and functions like {5:()=>console.log("five seconds!")} I want that this will always call the appropriate function based on the time but still taking to considerations interval hiccups + const currentTime = player.getCurrentTime(); + const timeInSeconds = Math.floor(currentTime); + if (keyframesFunctions[timeInSeconds]) { + keyframesFunctions[timeInSeconds](); + } + }, 1000); + } +} + + +export function startVideo(title,videoId, _keyframesFunctions){ + keyframesFunctions = _keyframesFunctions + const lastContainer = document.getElementById("youtube-container") + if(lastContainer){ + lastContainer.remove() + clearInterval(lastInterval) + } + const ytContainer = document.createElement('div'); + ytContainer.style.position="absolute" + ytContainer.style.top="50px" + ytContainer.style.left="50px" + ytContainer.style.zIndex="101" + ytContainer.setAttribute("id","youtube-container") + + + const videoContainerWindow = document.createElement('div'); + videoContainerWindow.style.width="100%" + videoContainerWindow.style.height="30px" + videoContainerWindow.style.background="#5e5e5e" + videoContainerWindow.style.cursor="move"; + videoContainerWindow.style.display= "flex"; + videoContainerWindow.style.alignItems= "center"; + const titleEl = document.createElement('span') + titleEl.style.flexGrow= "1"; + titleEl.style.padding= "4px"; + titleEl.appendChild(document.createTextNode(title)) + + videoContainerWindow.appendChild(titleEl); + //Add minimize and exit: + const minimizeButton = document.createElement('button'); + minimizeButton.style.marginRight = "5px"; + minimizeButton.innerHTML = "-"; + + minimizeButton.onclick = () => { + document.getElementById("player").hidden=!document.getElementById("player").hidden + } + + const exitButton = document.createElement('button'); + exitButton.innerHTML = "X"; + exitButton.style.marginRight="5px"; + + exitButton.onclick = () => { + ytContainer.remove(); + } + + videoContainerWindow.appendChild(minimizeButton); + videoContainerWindow.appendChild(exitButton); + + + + let drag = {x: 0, y: 0}; + let dragging = false; + videoContainerWindow.onmousedown = (e) => { + drag.x = e.clientX - ytContainer.offsetLeft; + drag.y = e.clientY - ytContainer.offsetTop; + dragging=true + } + + document.onmousemove = (e) => { + if (e.buttons === 1 && dragging) { + ytContainer.style.left = `${e.clientX - drag.x}px`; + ytContainer.style.top = `${e.clientY - drag.y}px`; + } + } + + document.onmouseup = (e) => { + drag = {x: 0, y: 0}; + dragging=false; + } + + + ytContainer.appendChild(videoContainerWindow) + const ytPlayer = document.createElement('div'); + ytPlayer.setAttribute("id","player") + ytContainer.appendChild(ytPlayer) + document.body.appendChild(ytContainer); + setVideo(videoId) +} \ No newline at end of file