From a528037105d373670ca6ecd9dc40934cc8923b36 Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 8 Nov 2023 17:19:21 +0800 Subject: [PATCH 001/127] feat: get avatar link button --- js/GetShareLink.js | 29 ++++++++++++ js/tw-styles.css | 108 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 js/GetShareLink.js diff --git a/js/GetShareLink.js b/js/GetShareLink.js new file mode 100644 index 0000000..d35db18 --- /dev/null +++ b/js/GetShareLink.js @@ -0,0 +1,29 @@ +import { van } from "./van.js"; +const { button, div, span, input } = van.tags; + +export function GetShareLink() { + return div( + { + class: () => + "absolute flex flex-col justify-center items-center top-0 left-0 bg-gray-900 bg-opacity-50 pointer-events-auto w-full h-full gap-2", + }, + span("We're launching OpenAI Assistant API integration soon!"), + div( + { + class: "w-[24rem] flex justify-center items-center", + }, + input({ + class: + "w-full input input-bordered text-black rounded rounded-l-md rounded-r-none", + placeholder: "Email", + }), + button( + { + class: + "btn rounded rounded-l-none rounded-r-md no-animation bg-neutral hover:bg-neutral-focus text-white border-none normal-case", + }, + "Get Avatar Link" + ) + ) + ); +} diff --git a/js/tw-styles.css b/js/tw-styles.css index bae1f9c..ec6db60 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1599,6 +1599,12 @@ details.collapse summary::-webkit-details-marker { border-radius: var(--rounded-box, 1rem); } +.no-animation { + --btn-focus-scale: 1; + --animation-btn: 0; + --animation-input: 0; +} + .btm-nav-xs > *:where(.active) { border-top-width: 1px; } @@ -1935,6 +1941,10 @@ details.collapse summary::-webkit-details-marker { height: 100%; } +.h-10 { + height: 2.5rem; +} + .w-3 { width: 0.75rem; } @@ -1956,6 +1966,22 @@ details.collapse summary::-webkit-details-marker { width: 100%; } +.w-20 { + width: 5rem; +} + +.w-96 { + width: 24rem; +} + +.w-\[28rem\] { + width: 28rem; +} + +.w-\[24rem\] { + width: 24rem; +} + .-translate-x-1\/2 { --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2022,6 +2048,55 @@ details.collapse summary::-webkit-details-marker { border-radius: 0.125rem; } +.rounded { + border-radius: 0.25rem; +} + +.rounded-s-md { + border-start-start-radius: 0.375rem; + border-end-start-radius: 0.375rem; +} + +.\!rounded-s-md { + border-start-start-radius: 0.375rem !important; + border-end-start-radius: 0.375rem !important; +} + +.rounded-s-lg { + border-start-start-radius: 0.5rem; + border-end-start-radius: 0.5rem; +} + +.rounded-r-none { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-l-none { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} + +.rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + .border-t-4 { border-top-width: 4px; } @@ -2065,6 +2140,16 @@ details.collapse summary::-webkit-details-marker { background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-neutral { + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } @@ -2092,6 +2177,10 @@ details.collapse summary::-webkit-details-marker { padding-bottom: 0.5rem; } +.text-center { + text-align: center; +} + .text-start { text-align: start; } @@ -2110,6 +2199,10 @@ details.collapse summary::-webkit-details-marker { font-weight: 700; } +.font-normal { + font-weight: 400; +} + .normal-case { text-transform: none; } @@ -2129,10 +2222,20 @@ details.collapse summary::-webkit-details-marker { color: rgb(194 65 12 / var(--tw-text-opacity)); } +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + .opacity-80 { opacity: 0.8; } +.outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} + .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -2295,6 +2398,11 @@ details.collapse summary::-webkit-details-marker { } } +.hover\:bg-neutral-focus:hover { + --tw-bg-opacity: 1; + background-color: hsl(var(--nf) / var(--tw-bg-opacity)); +} + .hover\:text-red-500:hover { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); From e5aa8fd373f40dd8b997065d83b8d639aa432d3a Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 8 Nov 2023 17:42:52 +0800 Subject: [PATCH 002/127] feat: app header --- js/AppHeader.js | 28 ++++++++++ js/Container.js | 2 + js/tw-styles.css | 141 +++++++++++++++++++++-------------------------- 3 files changed, 94 insertions(+), 77 deletions(-) create mode 100644 js/AppHeader.js diff --git a/js/AppHeader.js b/js/AppHeader.js new file mode 100644 index 0000000..9026e0a --- /dev/null +++ b/js/AppHeader.js @@ -0,0 +1,28 @@ +import { van } from "./van.js"; +const { div, span } = van.tags; + +export function AppHeader() { + return div( + { + class: () => "absolute flex justify-between top-0 w-full text-white p-4", + }, + div( + {}, + span( + { + class: + "block bg-gradient-to-b from-gray-500 to-white text-transparent bg-clip-text text-2xl", + }, + "Avatech v1" + ), + span( + { + class: + "bg-gradient-to-b from-gray-500 to-white text-transparent bg-clip-text text-lg", + }, + "Get your DALLE3 AI Personal Clone" + ) + ), + span({ class: "text-gray-300" }, "Twitter") + ); +} diff --git a/js/Container.js b/js/Container.js index 6f28cde..78bd798 100644 --- a/js/Container.js +++ b/js/Container.js @@ -4,6 +4,7 @@ import { van } from './van.js'; import { AvatarPreview } from './AvatarPreview.js'; import { Loading } from './Loading.js'; import { Alert } from './Alert.js'; +import { AppHeader } from './AppHeader.js'; const { button, iframe, div, img } = van.tags; export function Container() { @@ -17,5 +18,6 @@ export function Container() { AvatarPreview(), Loading(), Alert(), + AppHeader() ); } diff --git a/js/tw-styles.css b/js/tw-styles.css index ec6db60..f5d8f1a 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1921,6 +1921,10 @@ details.collapse summary::-webkit-details-marker { margin-top: 1rem; } +.block { + display: block; +} + .flex { display: flex; } @@ -1941,10 +1945,6 @@ details.collapse summary::-webkit-details-marker { height: 100%; } -.h-10 { - height: 2.5rem; -} - .w-3 { width: 0.75rem; } @@ -1953,6 +1953,10 @@ details.collapse summary::-webkit-details-marker { width: 14rem; } +.w-\[24rem\] { + width: 24rem; +} + .w-\[320px\] { width: 320px; } @@ -1966,22 +1970,6 @@ details.collapse summary::-webkit-details-marker { width: 100%; } -.w-20 { - width: 5rem; -} - -.w-96 { - width: 24rem; -} - -.w-\[28rem\] { - width: 28rem; -} - -.w-\[24rem\] { - width: 24rem; -} - .-translate-x-1\/2 { --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2032,6 +2020,10 @@ details.collapse summary::-webkit-details-marker { place-self: end; } +.rounded { + border-radius: 0.25rem; +} + .rounded-2xl { border-radius: 1rem; } @@ -2048,55 +2040,26 @@ details.collapse summary::-webkit-details-marker { border-radius: 0.125rem; } -.rounded { - border-radius: 0.25rem; -} - -.rounded-s-md { - border-start-start-radius: 0.375rem; - border-end-start-radius: 0.375rem; -} - -.\!rounded-s-md { - border-start-start-radius: 0.375rem !important; - border-end-start-radius: 0.375rem !important; -} - -.rounded-s-lg { - border-start-start-radius: 0.5rem; - border-end-start-radius: 0.5rem; -} - -.rounded-r-none { - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; -} - .rounded-l-md { border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; } -.rounded-l-lg { - border-top-left-radius: 0.5rem; - border-bottom-left-radius: 0.5rem; -} - .rounded-l-none { border-top-left-radius: 0px; border-bottom-left-radius: 0px; } -.rounded-r-lg { - border-top-right-radius: 0.5rem; - border-bottom-right-radius: 0.5rem; -} - .rounded-r-md { border-top-right-radius: 0.375rem; border-bottom-right-radius: 0.375rem; } +.rounded-r-none { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + .border-t-4 { border-top-width: 4px; } @@ -2130,6 +2093,11 @@ details.collapse summary::-webkit-details-marker { background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } +.bg-neutral { + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); +} + .bg-orange-100 { --tw-bg-opacity: 1; background-color: rgb(255 237 213 / var(--tw-bg-opacity)); @@ -2140,20 +2108,29 @@ details.collapse summary::-webkit-details-marker { background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } -.bg-gray-200 { - --tw-bg-opacity: 1; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)); -} - -.bg-neutral { - --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); -} - .bg-opacity-50 { --tw-bg-opacity: 0.5; } +.bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); +} + +.from-gray-500 { + --tw-gradient-from: #6b7280 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(107 114 128 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.to-white { + --tw-gradient-to: #fff var(--tw-gradient-to-position); +} + +.bg-clip-text { + -webkit-background-clip: text; + background-clip: text; +} + .p-0 { padding: 0px; } @@ -2162,6 +2139,10 @@ details.collapse summary::-webkit-details-marker { padding: 0.5rem; } +.p-4 { + padding: 1rem; +} + .\!py-0 { padding-top: 0px !important; padding-bottom: 0px !important; @@ -2177,10 +2158,6 @@ details.collapse summary::-webkit-details-marker { padding-bottom: 0.5rem; } -.text-center { - text-align: center; -} - .text-start { text-align: start; } @@ -2195,12 +2172,13 @@ details.collapse summary::-webkit-details-marker { line-height: 1.75rem; } -.font-bold { - font-weight: 700; +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; } -.font-normal { - font-weight: 400; +.font-bold { + font-weight: 700; } .normal-case { @@ -2227,13 +2205,22 @@ details.collapse summary::-webkit-details-marker { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.opacity-80 { - opacity: 0.8; +.text-transparent { + color: transparent; } -.outline-none { - outline: 2px solid transparent; - outline-offset: 2px; +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + +.opacity-80 { + opacity: 0.8; } .filter { From 9aeed4c321ad8763fae7b3b255a127c94f54d24e Mon Sep 17 00:00:00 2001 From: San Date: Thu, 9 Nov 2023 19:08:14 +0800 Subject: [PATCH 003/127] feat: funtional app --- js/AvatarPreview.js | 112 +++++- js/LayerEditor.js | 2 +- js/index.js | 29 +- js/styles.css | 10 + js/tw-styles.css | 938 ++++++++++++++++++++++++++++++-------------- tailwind.config.js | 3 + 6 files changed, 769 insertions(+), 325 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 1fe0e0a..98db43b 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -1,22 +1,104 @@ import { van } from "./van.js"; -const { button, iframe, div, img } = van.tags; -import { showEditor, previewUrl, showPreview } from "./state.js"; +import { imageUrl, showPreview, previewUrl } from "./state.js"; +const { button, iframe, div, img, input, label } = van.tags; export function AvatarPreview() { - return div( - { class: () => (!showEditor.val ? "" : "hidden") }, - iframe({ - id: "avatech-viewer-iframe", - title: "avatech-viewer-iframe", - name: "avatech-viewer-iframe", - allow: "cross-origin-isolated", + { class: () => - "w-[320px] h-[370px] absolute right-0 top-0 z-[100] pointer-events-auto flex mt-4 mr-4 rounded-2xl border-none " + - (showPreview.val ? "" : "hidden"), - // src: "https://labs.avatech.ai/viewer/default", - // src: "http://localhost:3000/viewer/default", - src: previewUrl, - }) + "w-full h-screen absolute left-0 top-0 z-[100] pointer-events-auto flex border-none ", + }, + button( + { + class: () => "absolute top-4 left-4 btn btn-outline w-24 text-xs !px-0 normal-case", + onclick: () => document.getElementById("comfy-load-button").click() + }, + "Load Shape flow", + ), + button( + { + class: () => "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", + onclick: () => window.open('https://twitter.com/avatech_gg', '_blank') + }, + "Twitter", + ), + div( + { + class: () => + "flex w-full h-full bg-white justify-around items-center p-24" + + (showPreview.val ? "" : "hidden"), + }, + div( + { + class: () => "flex flex-col bg-white justify-start h-full", + }, + div( + { + class: () => + " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", + }, + "Avatech v1", + ), + div( + { + class: () => + " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-4xl", + }, + "Get your DALLE3 AI Personal Clone", + ), + label( + { + class: () => + " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", + for: "imgtosam", + }, + div( + { + class: () => " absolute flex justify-center items-center ", + }, + "upload your image", + ), + img({ + class: () => " z-[10] object-contain w-[550px] h-[394px]", + id: "imgUrl", + }), + ), + div( + { + class: () => " w-full flex flex-col justify-center items-center", + }, + button( + { + class: () => "btn btn-outline mt-24 w-96 normal-case", + onclick: () => { + document.getElementById("sam").click(); + }, + }, + "Edit segment", + ), + button( + { + class: () => "btn btn-outline mt-4 w-96 normal-case", + onclick: () => { + document.getElementById("queue-button").click(); + }, + }, + "Generate", + ), + ), + ), + iframe({ + id: "avatech-viewer-iframe", + title: "avatech-viewer-iframe", + name: "avatech-viewer-iframe", + allow: "cross-origin-isolated", + class: () => + "w-[370px] h-[320px] z-[100] pointer-events-auto flex border-none " + + (showPreview.val ? "" : "hidden"), + // src: "https://labs.avatech.ai/viewer/default", + // src: "http://localhost:3000/viewer/default", + src: previewUrl, + }), + ), ); } diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 0e1438b..607fd65 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -114,7 +114,7 @@ export function LayerEditor() { return div( { class: () => - "absolute flex bg-gray-900 bg-opacity-50 top-0 w-full h-full pointer-events-auto " + + "absolute flex bg-gray-900 bg-opacity-50 top-0 w-full h-full pointer-events-auto z-[1000] " + (showImageEditor.val ? "" : "hidden"), }, button( diff --git a/js/index.js b/js/index.js index 8e6e671..ba592c2 100644 --- a/js/index.js +++ b/js/index.js @@ -330,6 +330,15 @@ const ext = { getCustomWidgets(app) { return { SAM_PROMPTS(node, inputName, inputData, app) { + const asd = document.createElement("div"); + Object.assign(asd, { + id: "sam", + onclick: () => { + showMyImageEditor(node); + }, + }); + document.body.append(asd); + const btn = node.addWidget("button", "Edit prompt", "", () => { showMyImageEditor(node); btn.serialize = false; @@ -690,9 +699,12 @@ function injectUIComponentToComfyuimenu() { .then((e) => e.arrayBuffer()) .then((e) => new Uint8Array(e)); - const labData = await fetch("https://labs.avatech.ai/api/share?id=" + previewModelId.val, { - method: "GET", - }).then((e) => e.json()); + const labData = await fetch( + "https://labs.avatech.ai/api/share?id=" + previewModelId.val, + { + method: "GET", + }, + ).then((e) => e.json()); await fetch(labData.url, { method: "PUT", @@ -704,13 +716,16 @@ function injectUIComponentToComfyuimenu() { body: file, }).catch((error) => console.error(error)); - await fetch("https://labs.avatech.ai/api/purgecdn?id=" + previewModelId.val, { - method: "GET", - }).catch((error) => console.error(error)); + await fetch( + "https://labs.avatech.ai/api/purgecdn?id=" + previewModelId.val, + { + method: "GET", + }, + ).catch((error) => console.error(error)); infoDialog.show( `Preview updated: https://editor.avatech.ai/viewer?objectId=` + - labData.modelId + + labData.modelId + "\n Remember to hard refresh before checking out the new preview!", ); diff --git a/js/styles.css b/js/styles.css index b07e30f..e69e65c 100644 --- a/js/styles.css +++ b/js/styles.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); @tailwind base; @tailwind components; @tailwind utilities; @@ -134,3 +135,12 @@ .comfy-list-actions button { font-size: 12px; } + +img { + display: none; +} + + +img[src] { + display: block; +} diff --git a/js/tw-styles.css b/js/tw-styles.css index f5d8f1a..0d5d8fc 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); + /* ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ @@ -435,16 +437,16 @@ video { } :root, -[data-theme] { +[data-theme]{ background-color: hsl(var(--b1) / var(--tw-bg-opacity, 1)); color: hsl(var(--bc) / var(--tw-text-opacity, 1)); } -html { +html{ -webkit-tap-highlight-color: transparent; } -:root { +:root{ color-scheme: light; --pf: 259 94% 44%; --sf: 314 100% 40%; @@ -482,7 +484,7 @@ html { --bc: 215 28% 17%; } -*, ::before, ::after { +*, ::before, ::after{ --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; @@ -532,7 +534,7 @@ html { --tw-backdrop-sepia: ; } -::backdrop { +::backdrop{ --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; @@ -582,39 +584,39 @@ html { --tw-backdrop-sepia: ; } -.avatar { +.avatar{ position: relative; display: inline-flex; } -.avatar > div { +.avatar > div{ display: block; aspect-ratio: 1 / 1; overflow: hidden; } -.avatar img { +.avatar img{ height: 100%; width: 100%; -o-object-fit: cover; object-fit: cover; } -.avatar.placeholder > div { +.avatar.placeholder > div{ display: flex; align-items: center; justify-content: center; } -@media (hover:hover) { - .label a:hover { +@media (hover:hover){ + .label a:hover{ --tw-text-opacity: 1; color: hsl(var(--bc) / var(--tw-text-opacity)); } .menu li > *:not(ul):not(.menu-title):not(details):active, .menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active { +.menu li > details > summary:active{ --tw-bg-opacity: 1; background-color: hsl(var(--n) / var(--tw-bg-opacity)); --tw-text-opacity: 1; @@ -622,7 +624,7 @@ html { } } -.btn { +.btn{ display: inline-flex; flex-shrink: 0; cursor: pointer; @@ -664,29 +666,29 @@ html { .btn-disabled, .btn[disabled], - .btn:disabled { + .btn:disabled{ pointer-events: none; } -.btn-circle { +.btn-circle{ height: 3rem; width: 3rem; border-radius: 9999px; padding: 0px; } -.btn-group > input[type="radio"].btn { +.btn-group > input[type="radio"].btn{ -webkit-appearance: none; -moz-appearance: none; appearance: none; } -.btn-group > input[type="radio"].btn:before { +.btn-group > input[type="radio"].btn:before{ content: attr(data-title); } .btn:is(input[type="checkbox"]), -.btn:is(input[type="radio"]) { +.btn:is(input[type="radio"]){ width: auto; -webkit-appearance: none; -moz-appearance: none; @@ -694,16 +696,16 @@ html { } .btn:is(input[type="checkbox"]):after, -.btn:is(input[type="radio"]):after { +.btn:is(input[type="radio"]):after{ --tw-content: attr(aria-label); content: var(--tw-content); } -.collapse:not(td):not(tr):not(colgroup) { +.collapse:not(td):not(tr):not(colgroup){ visibility: visible; } -.collapse { +.collapse{ position: relative; display: grid; overflow: hidden; @@ -716,13 +718,13 @@ html { .collapse-title, .collapse > input[type="checkbox"], .collapse > input[type="radio"], -.collapse-content { +.collapse-content{ grid-column-start: 1; grid-row-start: 1; } .collapse > input[type="checkbox"], -.collapse > input[type="radio"] { +.collapse > input[type="radio"]{ -webkit-appearance: none; -moz-appearance: none; appearance: none; @@ -731,12 +733,12 @@ html { .collapse[open], .collapse-open, -.collapse:focus:not(.collapse-close) { +.collapse:focus:not(.collapse-close){ grid-template-rows: auto 1fr; } .collapse:not(.collapse-close):has(> input[type="checkbox"]:checked), -.collapse:not(.collapse-close):has(> input[type="radio"]:checked) { +.collapse:not(.collapse-close):has(> input[type="radio"]:checked){ grid-template-rows: auto 1fr; } @@ -744,13 +746,13 @@ html { .collapse-open > .collapse-content, .collapse:focus:not(.collapse-close) > .collapse-content, .collapse:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-content, -.collapse:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content { +.collapse:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content{ visibility: visible; min-height: -moz-fit-content; min-height: fit-content; } -.divider { +.divider{ display: flex; flex-direction: row; align-items: center; @@ -762,28 +764,28 @@ html { } .divider:before, - .divider:after { + .divider:after{ content: ""; flex-grow: 1; height: 0.125rem; width: 100%; } -.dropdown { +.dropdown{ position: relative; display: inline-block; } -.dropdown > *:focus { +.dropdown > *:focus{ outline: 2px solid transparent; outline-offset: 2px; } -.dropdown .dropdown-content { +.dropdown .dropdown-content{ position: absolute; } -.dropdown:is(:not(details)) .dropdown-content { +.dropdown:is(:not(details)) .dropdown-content{ visibility: hidden; opacity: 0; transform-origin: top; @@ -800,38 +802,110 @@ html { .dropdown.dropdown-open .dropdown-content, .dropdown:not(.dropdown-hover):focus .dropdown-content, -.dropdown:focus-within .dropdown-content { +.dropdown:focus-within .dropdown-content{ visibility: visible; opacity: 1; } -@media (hover: hover) { - .dropdown.dropdown-hover:hover .dropdown-content { +@media (hover: hover){ + .dropdown.dropdown-hover:hover .dropdown-content{ visibility: visible; opacity: 1; } - .btn:hover { + .btn:hover{ --tw-border-opacity: 1; border-color: hsl(var(--b3) / var(--tw-border-opacity)); --tw-bg-opacity: 1; background-color: hsl(var(--b3) / var(--tw-bg-opacity)); } - .btn.glass:hover { + .btn.glass:hover{ --glass-opacity: 25%; --glass-border-opacity: 15%; } - .btn-ghost:hover { + .btn-ghost:hover{ --tw-border-opacity: 0; background-color: hsl(var(--bc) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.2; } + .btn-outline:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--b1) / var(--tw-text-opacity)); + } + + .btn-outline.btn-primary:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-secondary:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--sf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--sf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--sc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-accent:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--af) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--af) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--ac) / var(--tw-text-opacity)); + } + + .btn-outline.btn-success:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--su) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--su) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--suc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-info:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--in) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--in) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--inc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-warning:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--wa) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--wa) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--wac) / var(--tw-text-opacity)); + } + + .btn-outline.btn-error:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); + } + .btn-disabled:hover, .btn[disabled]:hover, - .btn:disabled:hover { + .btn:disabled:hover{ --tw-border-opacity: 0; background-color: hsl(var(--n) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.2; @@ -839,20 +913,20 @@ html { --tw-text-opacity: 0.2; } - .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover { + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ --tw-border-opacity: 1; border-color: hsl(var(--pf) / var(--tw-border-opacity)); --tw-bg-opacity: 1; background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } - .dropdown.dropdown-hover:hover .dropdown-content { + .dropdown.dropdown-hover:hover .dropdown-content{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover { + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ cursor: pointer; background-color: hsl(var(--bc) / 0.1); --tw-text-opacity: 1; @@ -862,11 +936,11 @@ html { } } -.dropdown:is(details) summary::-webkit-details-marker { +.dropdown:is(details) summary::-webkit-details-marker{ display: none; } -.label { +.label{ display: flex; -webkit-user-select: none; -moz-user-select: none; @@ -879,7 +953,7 @@ html { padding-bottom: 0.5rem; } -.input { +.input{ flex-shrink: 1; height: 3rem; padding-left: 1rem; @@ -895,29 +969,29 @@ html { border-radius: var(--rounded-btn, 0.5rem); } -.input-group > .input { +.input-group > .input{ isolation: isolate; } .input-group > *, .input-group > .input, .input-group > .textarea, - .input-group > .select { + .input-group > .select{ border-radius: 0px; } .join .dropdown .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .dropdown .join-item { + .join *:first-child:not(:last-child) .dropdown .join-item{ border-top-right-radius: inherit; border-bottom-right-radius: inherit; } -.link { +.link{ cursor: pointer; text-decoration-line: underline; } -.mask { +.mask{ -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; @@ -926,7 +1000,7 @@ html { mask-position: center; } -.menu { +.menu{ display: flex; flex-direction: column; flex-wrap: wrap; @@ -935,7 +1009,7 @@ html { padding: 0.5rem; } -.menu :where(li ul) { +.menu :where(li ul){ position: relative; white-space: nowrap; margin-left: 1rem; @@ -943,7 +1017,7 @@ html { } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), - .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ display: grid; grid-auto-flow: column; align-content: flex-start; @@ -955,7 +1029,7 @@ html { user-select: none; } -.menu li.disabled { +.menu li.disabled{ cursor: not-allowed; -webkit-user-select: none; -moz-user-select: none; @@ -963,11 +1037,11 @@ html { color: hsl(var(--bc) / 0.3); } -.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) { +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ display: none; } -:where(.menu li) { +:where(.menu li){ position: relative; display: flex; flex-shrink: 0; @@ -976,11 +1050,11 @@ html { align-items: stretch; } -:where(.menu li) .badge { +:where(.menu li) .badge{ justify-self: end; } -.modal { +.modal{ pointer-events: none; position: fixed; inset: 0px; @@ -1003,11 +1077,11 @@ html { overflow-y: hidden; } -:where(.modal) { +:where(.modal){ align-items: center; } -.modal-box { +.modal-box{ max-height: calc(100vh - 5em); grid-column-start: 1; grid-row-start: 1; @@ -1037,23 +1111,23 @@ html { .modal-open, .modal:target, .modal-toggle:checked + .modal, -.modal[open] { +.modal[open]{ pointer-events: auto; visibility: visible; opacity: 1; } -.modal-action { +.modal-action{ display: flex; margin-top: 1.5rem; justify-content: flex-end; } -:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) { +:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])){ overflow: hidden; } -.avatar-group :where(.avatar) { +.avatar-group :where(.avatar){ overflow: hidden; border-radius: 9999px; border-width: 4px; @@ -1061,42 +1135,42 @@ html { border-color: hsl(var(--b1) / var(--tw-border-opacity)); } -.btm-nav > *:where(.active) { +.btm-nav > *:where(.active){ border-top-width: 2px; --tw-bg-opacity: 1; background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } -.btm-nav > * .label { +.btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; } .btn:active:hover, - .btn:active:focus { + .btn:active:focus{ animation: button-pop 0s ease-out; transform: scale(var(--btn-focus-scale, 0.97)); } -.btn:focus-visible { +.btn:focus-visible{ outline-style: solid; outline-width: 2px; outline-offset: 2px; } -.btn.glass { +.btn.glass{ --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); outline-color: currentColor; } -.btn.glass.btn-active { +.btn.glass.btn-active{ --glass-opacity: 25%; --glass-border-opacity: 15%; } -.btn-ghost { +.btn-ghost{ border-width: 1px; border-color: transparent; background-color: transparent; @@ -1107,15 +1181,132 @@ html { outline-color: currentColor; } -.btn-ghost.btn-active { +.btn-ghost.btn-active{ --tw-border-opacity: 0; background-color: hsl(var(--bc) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.2; } +.btn-outline{ + border-color: currentColor; + background-color: transparent; + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.btn-outline.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--b1) / var(--tw-text-opacity)); +} + +.btn-outline.btn-primary{ + --tw-text-opacity: 1; + color: hsl(var(--p) / var(--tw-text-opacity)); +} + +.btn-outline.btn-primary.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-secondary{ + --tw-text-opacity: 1; + color: hsl(var(--s) / var(--tw-text-opacity)); +} + +.btn-outline.btn-secondary.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--sf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--sf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--sc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-accent{ + --tw-text-opacity: 1; + color: hsl(var(--a) / var(--tw-text-opacity)); +} + +.btn-outline.btn-accent.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--af) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--af) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--ac) / var(--tw-text-opacity)); +} + +.btn-outline.btn-success{ + --tw-text-opacity: 1; + color: hsl(var(--su) / var(--tw-text-opacity)); +} + +.btn-outline.btn-success.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--su) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--su) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--suc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-info{ + --tw-text-opacity: 1; + color: hsl(var(--in) / var(--tw-text-opacity)); +} + +.btn-outline.btn-info.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--in) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--in) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--inc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-warning{ + --tw-text-opacity: 1; + color: hsl(var(--wa) / var(--tw-text-opacity)); +} + +.btn-outline.btn-warning.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--wa) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--wa) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--wac) / var(--tw-text-opacity)); +} + +.btn-outline.btn-error{ + --tw-text-opacity: 1; + color: hsl(var(--er) / var(--tw-text-opacity)); +} + +.btn-outline.btn-error.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); +} + .btn.btn-disabled, .btn[disabled], - .btn:disabled { + .btn:disabled{ --tw-border-opacity: 0; background-color: hsl(var(--n) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.2; @@ -1124,7 +1315,7 @@ html { } .btn-group > input[type="radio"]:checked.btn, - .btn-group > .btn-active { + .btn-group > .btn-active{ --tw-border-opacity: 1; border-color: hsl(var(--p) / var(--tw-border-opacity)); --tw-bg-opacity: 1; @@ -1133,14 +1324,14 @@ html { color: hsl(var(--pc) / var(--tw-text-opacity)); } -.btn-group > input[type="radio"]:checked.btn:focus-visible, .btn-group > .btn-active:focus-visible { +.btn-group > input[type="radio"]:checked.btn:focus-visible, .btn-group > .btn-active:focus-visible{ outline-style: solid; outline-width: 2px; outline-color: hsl(var(--p) / 1); } .btn:is(input[type="checkbox"]:checked), -.btn:is(input[type="radio"]:checked) { +.btn:is(input[type="radio"]:checked){ --tw-border-opacity: 1; border-color: hsl(var(--p) / var(--tw-border-opacity)); --tw-bg-opacity: 1; @@ -1149,54 +1340,54 @@ html { color: hsl(var(--pc) / var(--tw-text-opacity)); } -.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible { +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ outline-color: hsl(var(--p) / 1); } -@keyframes button-pop { - 0% { +@keyframes button-pop{ + 0%{ transform: scale(var(--btn-focus-scale, 0.98)); } - 40% { + 40%{ transform: scale(1.02); } - 100% { + 100%{ transform: scale(1); } } -@keyframes checkmark { - 0% { +@keyframes checkmark{ + 0%{ background-position-y: 5px; } - 50% { + 50%{ background-position-y: -2px; } - 100% { + 100%{ background-position-y: 0; } } -details.collapse { +details.collapse{ width: 100%; } -details.collapse summary { +details.collapse summary{ position: relative; display: block; outline: 2px solid transparent; outline-offset: 2px; } -details.collapse summary::-webkit-details-marker { +details.collapse summary::-webkit-details-marker{ display: none; } -.collapse:focus-visible { +.collapse:focus-visible{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1205,7 +1396,7 @@ details.collapse summary::-webkit-details-marker { .collapse:has(.collapse-title:focus-visible), .collapse:has(> input[type="checkbox"]:focus-visible), -.collapse:has(> input[type="radio"]:focus-visible) { +.collapse:has(> input[type="radio"]:focus-visible){ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1214,22 +1405,22 @@ details.collapse summary::-webkit-details-marker { .collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], .collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), -.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title { +.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title{ cursor: pointer; } -.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title { +.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title{ cursor: unset; } :where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]) { +:where(.collapse > input[type="radio"]){ z-index: 1; } .collapse-title, :where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]) { +:where(.collapse > input[type="radio"]){ width: 100%; padding: 1rem; padding-right: 3rem; @@ -1241,7 +1432,7 @@ details.collapse summary::-webkit-details-marker { .collapse-open > :where(.collapse-content), .collapse:focus:not(.collapse-close) > :where(.collapse-content), .collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), -.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content) { +.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content){ padding-bottom: 1rem; transition: padding 0.2s ease-out, background-color 0.2s ease-out; @@ -1251,7 +1442,7 @@ details.collapse summary::-webkit-details-marker { .collapse-open.collapse-arrow > .collapse-title:after, .collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, .collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after { +.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ --tw-translate-y: -50%; --tw-rotate: 225deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1263,7 +1454,7 @@ details.collapse summary::-webkit-details-marker { [dir="rtl"] .collapse-arrow:not(.collapse-close) input[type="checkbox"]:checked - ~ .collapse-title:after { + ~ .collapse-title:after{ --tw-rotate: 135deg; } @@ -1271,41 +1462,41 @@ details.collapse summary::-webkit-details-marker { .collapse-open.collapse-plus > .collapse-title:after, .collapse-plus:focus:not(.collapse-close) > .collapse-title:after, .collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after { +.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ content: "−"; } -.divider:before { +.divider:before{ background-color: hsl(var(--bc) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.1; } -.divider:after { +.divider:after{ background-color: hsl(var(--bc) / var(--tw-bg-opacity)); --tw-bg-opacity: 0.1; } -.divider:not(:empty) { +.divider:not(:empty){ gap: 1rem; } .dropdown.dropdown-open .dropdown-content, .dropdown:focus .dropdown-content, -.dropdown:focus-within .dropdown-content { +.dropdown:focus-within .dropdown-content{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.input[list]::-webkit-calendar-picker-indicator { +.input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } -.input-bordered { +.input-bordered{ --tw-border-opacity: 0.2; } -.input:focus { +.input:focus{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1314,7 +1505,7 @@ details.collapse summary::-webkit-details-marker { .input-disabled, .input:disabled, - .input[disabled] { + .input[disabled]{ cursor: not-allowed; --tw-border-opacity: 1; border-color: hsl(var(--b2) / var(--tw-border-opacity)); @@ -1323,29 +1514,29 @@ details.collapse summary::-webkit-details-marker { --tw-text-opacity: 0.2; } -.input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder { +.input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder{ color: hsl(var(--bc) / var(--tw-placeholder-opacity)); --tw-placeholder-opacity: 0.2; } .input-disabled::placeholder, .input:disabled::placeholder, - .input[disabled]::placeholder { + .input[disabled]::placeholder{ color: hsl(var(--bc) / var(--tw-placeholder-opacity)); --tw-placeholder-opacity: 0.2; } -.link:focus { +.link:focus{ outline: 2px solid transparent; outline-offset: 2px; } -.link:focus-visible { +.link:focus-visible{ outline: 2px solid currentColor; outline-offset: 2px; } -.loading { +.loading{ pointer-events: none; display: inline-block; aspect-ratio: 1 / 1; @@ -1361,22 +1552,22 @@ details.collapse summary::-webkit-details-marker { mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.loading-spinner { +.loading-spinner{ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.loading-lg { +.loading-lg{ width: 2.5rem; } -:where(.menu li:empty) { +:where(.menu li:empty){ background-color: hsl(var(--bc) / 0.1); margin: 0.5rem 1rem; height: 1px; } -.menu :where(li ul):before { +.menu :where(li ul):before{ position: absolute; left: 0px; top: 0.75rem; @@ -1387,7 +1578,7 @@ details.collapse summary::-webkit-details-marker { } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), -.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ padding-left: 1rem; padding-right: 1rem; padding-top: 0.5rem; @@ -1408,7 +1599,7 @@ details.collapse summary::-webkit-details-marker { :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible { + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ cursor: pointer; background-color: hsl(var(--bc) / 0.1); --tw-text-opacity: 1; @@ -1419,19 +1610,19 @@ details.collapse summary::-webkit-details-marker { .menu li > *:not(ul):not(.menu-title):not(details):active, .menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active { +.menu li > details > summary:active{ --tw-bg-opacity: 1; background-color: hsl(var(--n) / var(--tw-bg-opacity)); --tw-text-opacity: 1; color: hsl(var(--nc) / var(--tw-text-opacity)); } -.menu :where(li > details > summary)::-webkit-details-marker { +.menu :where(li > details > summary)::-webkit-details-marker{ display: none; } .menu :where(li > details > summary):after, -.menu :where(li > .menu-dropdown-toggle):after { +.menu :where(li > .menu-dropdown-toggle):after{ justify-self: end; display: block; margin-top: -0.5rem; @@ -1448,18 +1639,18 @@ details.collapse summary::-webkit-details-marker { } .menu :where(li > details[open] > summary):after, -.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after{ transform: rotate(225deg); margin-top: 0; } -.mockup-phone .display { +.mockup-phone .display{ overflow: hidden; border-radius: 40px; margin-top: -25px; } -.mockup-browser .mockup-browser-toolbar .input { +.mockup-browser .mockup-browser-toolbar .input{ position: relative; margin-left: auto; margin-right: auto; @@ -1474,7 +1665,7 @@ details.collapse summary::-webkit-details-marker { padding-left: 2rem; } -.mockup-browser .mockup-browser-toolbar .input:before { +.mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; top: 50%; @@ -1489,7 +1680,7 @@ details.collapse summary::-webkit-details-marker { opacity: 0.6; } -.mockup-browser .mockup-browser-toolbar .input:after { +.mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; top: 50%; @@ -1505,7 +1696,7 @@ details.collapse summary::-webkit-details-marker { } .modal:not(dialog:not(.modal-open)), - .modal::backdrop { + .modal::backdrop{ background-color: rgba(0, 0, 0, 0.3); animation: modal-pop 0.2s ease-out; } @@ -1513,115 +1704,115 @@ details.collapse summary::-webkit-details-marker { .modal-open .modal-box, .modal-toggle:checked + .modal .modal-box, .modal:target .modal-box, -.modal[open] .modal-box { +.modal[open] .modal-box{ --tw-translate-y: 0px; --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.modal-action > :not([hidden]) ~ :not([hidden]) { +.modal-action > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } -@keyframes modal-pop { - 0% { +@keyframes modal-pop{ + 0%{ opacity: 0; } } -@keyframes progress-loading { - 50% { +@keyframes progress-loading{ + 50%{ background-position-x: -115%; } } -@keyframes radiomark { - 0% { +@keyframes radiomark{ + 0%{ box-shadow: 0 0 0 12px hsl(var(--b1)) inset, 0 0 0 12px hsl(var(--b1)) inset; } - 50% { + 50%{ box-shadow: 0 0 0 3px hsl(var(--b1)) inset, 0 0 0 3px hsl(var(--b1)) inset; } - 100% { + 100%{ box-shadow: 0 0 0 4px hsl(var(--b1)) inset, 0 0 0 4px hsl(var(--b1)) inset; } } -@keyframes rating-pop { - 0% { +@keyframes rating-pop{ + 0%{ transform: translateY(-0.125em); } - 40% { + 40%{ transform: translateY(-0.125em); } - 100% { + 100%{ transform: translateY(0); } } .table tr.active, .table tr.active:nth-child(even), - .table-zebra tbody tr:nth-child(even) { + .table-zebra tbody tr:nth-child(even){ --tw-bg-opacity: 1; background-color: hsl(var(--b2) / var(--tw-bg-opacity)); } .table-zebra tr.active, .table-zebra tr.active:nth-child(even), - .table-zebra-zebra tbody tr:nth-child(even) { + .table-zebra-zebra tbody tr:nth-child(even){ --tw-bg-opacity: 1; background-color: hsl(var(--b3) / var(--tw-bg-opacity)); } -@keyframes toast-pop { - 0% { +@keyframes toast-pop{ + 0%{ transform: scale(0.9); opacity: 0; } - 100% { + 100%{ transform: scale(1); opacity: 1; } } -.rounded-box { +.rounded-box{ border-radius: var(--rounded-box, 1rem); } -.no-animation { +.no-animation{ --btn-focus-scale: 1; --animation-btn: 0; --animation-input: 0; } -.btm-nav-xs > *:where(.active) { +.btm-nav-xs > *:where(.active){ border-top-width: 1px; } -.btm-nav-sm > *:where(.active) { +.btm-nav-sm > *:where(.active){ border-top-width: 2px; } -.btm-nav-md > *:where(.active) { +.btm-nav-md > *:where(.active){ border-top-width: 2px; } -.btm-nav-lg > *:where(.active) { +.btm-nav-lg > *:where(.active){ border-top-width: 4px; } -.btn-xs { +.btn-xs{ height: 1.5rem; padding-left: 0.5rem; padding-right: 0.5rem; @@ -1629,7 +1820,7 @@ details.collapse summary::-webkit-details-marker { font-size: 0.75rem; } -.btn-sm { +.btn-sm{ height: 2rem; padding-left: 0.75rem; padding-right: 0.75rem; @@ -1637,51 +1828,51 @@ details.collapse summary::-webkit-details-marker { font-size: 0.875rem; } -.btn-square:where(.btn-xs) { +.btn-square:where(.btn-xs){ height: 1.5rem; width: 1.5rem; padding: 0px; } -.btn-square:where(.btn-sm) { +.btn-square:where(.btn-sm){ height: 2rem; width: 2rem; padding: 0px; } -.btn-circle:where(.btn-xs) { +.btn-circle:where(.btn-xs){ height: 1.5rem; width: 1.5rem; border-radius: 9999px; padding: 0px; } -.btn-circle:where(.btn-sm) { +.btn-circle:where(.btn-sm){ height: 2rem; width: 2rem; border-radius: 9999px; padding: 0px; } -.btn-circle:where(.btn-md) { +.btn-circle:where(.btn-md){ height: 3rem; width: 3rem; border-radius: 9999px; padding: 0px; } -.btn-circle:where(.btn-lg) { +.btn-circle:where(.btn-lg){ height: 4rem; width: 4rem; border-radius: 9999px; padding: 0px; } -.modal-bottom { +.modal-bottom{ place-items: end; } -.avatar.online:before { +.avatar.online:before{ content: ""; position: absolute; z-index: 10; @@ -1698,7 +1889,7 @@ details.collapse summary::-webkit-details-marker { right: 7%; } -.avatar.offline:before { +.avatar.offline:before{ content: ""; position: absolute; z-index: 10; @@ -1715,14 +1906,14 @@ details.collapse summary::-webkit-details-marker { right: 7%; } -.btn-group .btn:not(:first-child):not(:last-child) { +.btn-group .btn:not(:first-child):not(:last-child){ border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.btn-group .btn:first-child:not(:last-child) { +.btn-group .btn:first-child:not(:last-child){ margin-top: -0px; margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); @@ -1731,21 +1922,21 @@ details.collapse summary::-webkit-details-marker { border-bottom-right-radius: 0; } -.btn-group .btn:last-child:not(:first-child) { +.btn-group .btn:last-child:not(:first-child){ border-top-left-radius: 0; border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; border-bottom-right-radius: var(--rounded-btn, 0.5rem); } -.btn-group-horizontal .btn:not(:first-child):not(:last-child) { +.btn-group-horizontal .btn:not(:first-child):not(:last-child){ border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.btn-group-horizontal .btn:first-child:not(:last-child) { +.btn-group-horizontal .btn:first-child:not(:last-child){ margin-top: -0px; margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); @@ -1754,14 +1945,14 @@ details.collapse summary::-webkit-details-marker { border-bottom-right-radius: 0; } -.btn-group-horizontal .btn:last-child:not(:first-child) { +.btn-group-horizontal .btn:last-child:not(:first-child){ border-top-left-radius: 0; border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; border-bottom-right-radius: var(--rounded-btn, 0.5rem); } -.btn-group-vertical .btn:first-child:not(:last-child) { +.btn-group-vertical .btn:first-child:not(:last-child){ margin-top: -1px; margin-left: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); @@ -1770,14 +1961,14 @@ details.collapse summary::-webkit-details-marker { border-bottom-right-radius: 0; } -.btn-group-vertical .btn:last-child:not(:first-child) { +.btn-group-vertical .btn:last-child:not(:first-child){ border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); border-bottom-right-radius: var(--rounded-btn, 0.5rem); } -.modal-top :where(.modal-box) { +.modal-top :where(.modal-box){ width: 100%; max-width: none; --tw-translate-y: -2.5rem; @@ -1790,7 +1981,7 @@ details.collapse summary::-webkit-details-marker { border-bottom-right-radius: var(--rounded-box, 1rem); } -.modal-middle :where(.modal-box) { +.modal-middle :where(.modal-box){ width: 91.666667%; max-width: 32rem; --tw-translate-y: 0px; @@ -1803,7 +1994,7 @@ details.collapse summary::-webkit-details-marker { border-bottom-right-radius: var(--rounded-box, 1rem); } -.modal-bottom :where(.modal-box) { +.modal-bottom :where(.modal-box){ width: 100%; max-width: none; --tw-translate-y: 2.5rem; @@ -1816,414 +2007,549 @@ details.collapse summary::-webkit-details-marker { border-top-right-radius: var(--rounded-box, 1rem); } -.pointer-events-none { +.pointer-events-none{ pointer-events: none; } -.pointer-events-auto { +.pointer-events-auto{ pointer-events: auto; } -.visible { +.visible{ visibility: visible; } -.collapse { +.collapse{ visibility: collapse; } -.fixed { +.fixed{ position: fixed; } -.absolute { +.absolute{ position: absolute; } -.relative { +.relative{ position: relative; } -.bottom-0 { +.bottom-0{ bottom: 0px; } -.bottom-8 { +.bottom-8{ bottom: 2rem; } -.left-0 { +.left-0{ left: 0px; } -.left-1\/2 { +.left-1\/2{ left: 50%; } -.left-2 { +.left-2{ left: 0.5rem; } -.right-0 { +.right-0{ right: 0px; } -.right-2 { +.right-2{ right: 0.5rem; } -.top-0 { +.top-0{ top: 0px; } -.top-1\/2 { +.top-1\/2{ top: 50%; } -.top-2 { +.top-2{ top: 0.5rem; } -.z-\[1000\] { +.left-4{ + left: 1rem; +} + +.top-4{ + top: 1rem; +} + +.right-4{ + right: 1rem; +} + +.z-\[1000\]{ z-index: 1000; } -.z-\[100\] { +.z-\[100\]{ z-index: 100; } -.z-\[200\] { +.z-\[10\]{ + z-index: 10; +} + +.z-\[200\]{ z-index: 200; } -.my-0 { +.my-0{ margin-top: 0px; margin-bottom: 0px; } -.mb-2 { +.mb-2{ margin-bottom: 0.5rem; } -.ml-2 { +.ml-2{ margin-left: 0.5rem; } -.mr-4 { - margin-right: 1rem; -} - -.mt-2 { +.mt-2{ margin-top: 0.5rem; } -.mt-4 { +.mt-24{ + margin-top: 6rem; +} + +.mt-4{ margin-top: 1rem; } -.block { +.block{ display: block; } -.flex { +.flex{ display: flex; } -.hidden { +.hidden{ display: none; } -.h-3 { +.aspect-square{ + aspect-ratio: 1 / 1; +} + +.h-3{ height: 0.75rem; } -.h-\[370px\] { - height: 370px; +.h-\[320px\]{ + height: 320px; } -.h-full { +.h-\[450px\]{ + height: 450px; +} + +.h-full{ height: 100%; } -.w-3 { +.h-screen{ + height: 100vh; +} + +.h-\[397px\]{ + height: 397px; +} + +.h-\[394px\]{ + height: 394px; +} + +.w-3{ width: 0.75rem; } -.w-56 { +.w-56{ width: 14rem; } -.w-\[24rem\] { +.w-96{ width: 24rem; } -.w-\[320px\] { - width: 320px; +.w-\[24rem\]{ + width: 24rem; } -.w-fit { +.w-\[370px\]{ + width: 370px; +} + +.w-\[550px\]{ + width: 550px; +} + +.w-fit{ width: -moz-fit-content; width: fit-content; } -.w-full { +.w-full{ width: 100%; } -.-translate-x-1\/2 { +.w-24{ + width: 6rem; +} + +.w-36{ + width: 9rem; +} + +.-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.-translate-y-1\/2 { +.-translate-y-1\/2{ --tw-translate-y: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.transform { +.transform{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.flex-row { +.cursor-pointer{ + cursor: pointer; +} + +.flex-row{ flex-direction: row; } -.flex-col { +.flex-col{ flex-direction: column; } -.items-start { +.items-start{ align-items: flex-start; } -.items-center { +.items-center{ align-items: center; } -.justify-center { +.justify-start{ + justify-content: flex-start; +} + +.justify-center{ justify-content: center; } -.justify-between { +.justify-between{ justify-content: space-between; } -.gap-2 { +.justify-around{ + justify-content: space-around; +} + +.gap-2{ gap: 0.5rem; } -.gap-4 { +.gap-4{ gap: 1rem; } -.place-self-end { +.place-self-end{ place-self: end; } -.rounded { +.rounded{ border-radius: 0.25rem; } -.rounded-2xl { - border-radius: 1rem; -} - -.rounded-full { +.rounded-full{ border-radius: 9999px; } -.rounded-md { +.rounded-lg{ + border-radius: 0.5rem; +} + +.rounded-md{ border-radius: 0.375rem; } -.rounded-sm { +.rounded-sm{ border-radius: 0.125rem; } -.rounded-l-md { +.rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; } -.rounded-l-none { +.rounded-l-none{ border-top-left-radius: 0px; border-bottom-left-radius: 0px; } -.rounded-r-md { +.rounded-r-md{ border-top-right-radius: 0.375rem; border-bottom-right-radius: 0.375rem; } -.rounded-r-none { +.rounded-r-none{ border-top-right-radius: 0px; border-bottom-right-radius: 0px; } -.border-t-4 { +.border-0{ + border-width: 0px; +} + +.border-t-4{ border-top-width: 4px; } -.border-none { +.border-none{ border-style: none; } -.border-orange-500 { +.border-orange-500{ --tw-border-opacity: 1; border-color: rgb(249 115 22 / var(--tw-border-opacity)); } -.bg-base-200 { +.bg-\[\#f6f6f6\]{ + --tw-bg-opacity: 1; + background-color: rgb(246 246 246 / var(--tw-bg-opacity)); +} + +.bg-base-200{ --tw-bg-opacity: 1; background-color: hsl(var(--b2) / var(--tw-bg-opacity)); } -.bg-black { +.bg-black{ --tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity)); } -.bg-gray-900 { +.bg-gray-900{ --tw-bg-opacity: 1; background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } -.bg-green-500 { +.bg-green-500{ --tw-bg-opacity: 1; background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } -.bg-neutral { +.bg-neutral{ --tw-bg-opacity: 1; background-color: hsl(var(--n) / var(--tw-bg-opacity)); } -.bg-orange-100 { +.bg-orange-100{ --tw-bg-opacity: 1; background-color: rgb(255 237 213 / var(--tw-bg-opacity)); } -.bg-red-500 { +.bg-red-500{ --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } -.bg-opacity-50 { +.bg-white{ + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-opacity-50{ --tw-bg-opacity: 0.5; } -.bg-gradient-to-b { +.bg-gradient-to-b{ background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); } -.from-gray-500 { +.from-black{ + --tw-gradient-from: #000 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-gray-500{ --tw-gradient-from: #6b7280 var(--tw-gradient-from-position); --tw-gradient-to: rgb(107 114 128 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); } -.to-white { +.via-\[\#5F5F5F\]{ + --tw-gradient-to: rgb(95 95 95 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #5F5F5F var(--tw-gradient-via-position), var(--tw-gradient-to); +} + +.via-50\%{ + --tw-gradient-via-position: 50%; +} + +.via-60\%{ + --tw-gradient-via-position: 60%; +} + +.to-white{ --tw-gradient-to: #fff var(--tw-gradient-to-position); } -.bg-clip-text { +.bg-clip-text{ -webkit-background-clip: text; background-clip: text; } -.p-0 { +.object-contain{ + -o-object-fit: contain; + object-fit: contain; +} + +.p-0{ padding: 0px; } -.p-2 { +.p-2{ padding: 0.5rem; } -.p-4 { +.p-24{ + padding: 6rem; +} + +.p-4{ padding: 1rem; } -.\!py-0 { +.\!py-0{ padding-top: 0px !important; padding-bottom: 0px !important; } -.px-4 { +.px-4{ padding-left: 1rem; padding-right: 1rem; } -.py-2 { +.py-2{ padding-top: 0.5rem; padding-bottom: 0.5rem; } -.text-start { +.\!px-0{ + padding-left: 0px !important; + padding-right: 0px !important; +} + +.pb-24{ + padding-bottom: 6rem; +} + +.text-start{ text-align: start; } -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; +.font-gabarito{ + font-family: Gabarito; } -.text-xl { - font-size: 1.25rem; - line-height: 1.75rem; -} - -.text-2xl { +.text-2xl{ font-size: 1.5rem; line-height: 2rem; } -.font-bold { +.text-4xl{ + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-lg{ + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-xl{ + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs{ + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold{ font-weight: 700; } -.normal-case { +.normal-case{ text-transform: none; } -.text-base-content { +.text-base-content{ --tw-text-opacity: 1; color: hsl(var(--bc) / var(--tw-text-opacity)); } -.text-black { +.text-black{ --tw-text-opacity: 1; color: rgb(0 0 0 / var(--tw-text-opacity)); } -.text-orange-700 { - --tw-text-opacity: 1; - color: rgb(194 65 12 / var(--tw-text-opacity)); -} - -.text-white { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); -} - -.text-transparent { - color: transparent; -} - -.text-gray-500 { - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); -} - -.text-gray-300 { +.text-gray-300{ --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); } -.opacity-80 { +.text-orange-700{ + --tw-text-opacity: 1; + color: rgb(194 65 12 / var(--tw-text-opacity)); +} + +.text-transparent{ + color: transparent; +} + +.text-white{ + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.opacity-80{ opacity: 0.8; } -.filter { +.ring-1{ + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-black{ + --tw-ring-opacity: 1; + --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); +} + +.filter{ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -2366,12 +2692,20 @@ details.collapse summary::-webkit-details-marker { font-size: 12px; } -@media (min-width: 640px) { - .sm\:modal-middle { +img { + display: none; +} + +img[src] { + display: block; +} + +@media (min-width: 640px){ + .sm\:modal-middle{ place-items: center; } - .sm\:modal-middle :where(.modal-box) { + .sm\:modal-middle :where(.modal-box){ width: 91.666667%; max-width: 32rem; --tw-translate-y: 0px; @@ -2385,12 +2719,12 @@ details.collapse summary::-webkit-details-marker { } } -.hover\:bg-neutral-focus:hover { +.hover\:bg-neutral-focus:hover{ --tw-bg-opacity: 1; background-color: hsl(var(--nf) / var(--tw-bg-opacity)); } -.hover\:text-red-500:hover { +.hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); } diff --git a/tailwind.config.js b/tailwind.config.js index 8214ba5..d183623 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,6 +3,9 @@ module.exports = { // content: ['./js/**/*.{html,js}'], content: ['./js/**/*.{html,js}'], theme: { + fontFamily: { + 'gabarito': ['Gabarito'], + }, extend: {}, }, daisyui: { From ae2bd4ff2dea5b0150aafffe81f43890a2fbcb75 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 9 Nov 2023 09:02:42 -0800 Subject: [PATCH 004/127] fix file upload + add close button --- js/AvatarPreview.js | 82 ++++++++++++++++++++++++++++++++------------- js/state.js | 3 +- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 98db43b..7e6f916 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -1,26 +1,49 @@ import { van } from "./van.js"; -import { imageUrl, showPreview, previewUrl } from "./state.js"; +import { imageUrl, showPreview, previewUrl, showEditor } from "./state.js"; const { button, iframe, div, img, input, label } = van.tags; +import { app } from "./app.js"; export function AvatarPreview() { return div( { - class: () => - "w-full h-screen absolute left-0 top-0 z-[100] pointer-events-auto flex border-none ", - }, - button( - { - class: () => "absolute top-4 left-4 btn btn-outline w-24 text-xs !px-0 normal-case", - onclick: () => document.getElementById("comfy-load-button").click() + class: () => { + console.log(showPreview); + + return ( + (showPreview.val ? "" : "hidden ") + + "w-full h-screen absolute left-0 top-0 z-[100] pointer-events-auto flex border-none " + ); }, - "Load Shape flow", + }, + div( + { + class: "absolute top-4 left-4 flex flex-col gap-2", + }, + button( + { + class: () => " btn btn-outline w-24 text-xs !px-0 normal-case", + onclick: () => { + // console.log(showEditor); + showPreview.val = false; + }, + }, + "X" + ), + button( + { + class: () => "btn btn-outline w-24 text-xs !px-0 normal-case", + onclick: () => document.getElementById("comfy-load-button").click(), + }, + "Load Shape flow" + ) ), button( { - class: () => "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", - onclick: () => window.open('https://twitter.com/avatech_gg', '_blank') + class: () => + "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", + onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter", + "Twitter" ), div( { @@ -37,31 +60,42 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1", + "Avatech v1" ), div( { class: () => - " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-4xl", + " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone", + "Get your DALLE3 AI Personal Clone" ), label( { class: () => " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", - for: "imgtosam", + // for: "imgtosam", + onclick: () => { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const nodes = graph.findNodesByType("LoadImage"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find(x => x.value == "image").callback() + }, }, div( { class: () => " absolute flex justify-center items-center ", }, - "upload your image", + "upload your image" ), img({ class: () => " z-[10] object-contain w-[550px] h-[394px]", id: "imgUrl", - }), + }) ), div( { @@ -74,7 +108,7 @@ export function AvatarPreview() { document.getElementById("sam").click(); }, }, - "Edit segment", + "Edit segment" ), button( { @@ -83,9 +117,9 @@ export function AvatarPreview() { document.getElementById("queue-button").click(); }, }, - "Generate", - ), - ), + "Generate" + ) + ) ), iframe({ id: "avatech-viewer-iframe", @@ -98,7 +132,7 @@ export function AvatarPreview() { // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", src: previewUrl, - }), - ), + }) + ) ); } diff --git a/js/state.js b/js/state.js index d70f4e1..011b781 100644 --- a/js/state.js +++ b/js/state.js @@ -16,7 +16,8 @@ import { van } from "./van.js"; export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); -export const showPreview = van.state(localStorage.getItem("showPreview") == 'true'); +// localStorage.getItem("showPreview") == 'true' +export const showPreview = van.state(true); export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=300&height=300&hideTrigger=true"); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); From e783169a26a59e5e3c6bb47972e3ae96407b8186 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 9 Nov 2023 11:23:41 -0800 Subject: [PATCH 005/127] fix loading styles --- js/Loading.js | 2 +- js/tw-styles.css | 58 ++++++++++++++++++------------------------------ 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/js/Loading.js b/js/Loading.js index 27b01fa..9ff88e9 100644 --- a/js/Loading.js +++ b/js/Loading.js @@ -6,7 +6,7 @@ export function Loading() { return div( { class: () => - "absolute flex flex-col justify-center items-center top-0 left-0 bg-gray-900 bg-opacity-50 pointer-events-auto w-full h-full " + + "absolute flex flex-col justify-center items-center top-0 left-0 bg-gray-900 bg-opacity-50 pointer-events-auto w-full h-full z-[1001] " + (showLoading.val ? "" : "hidden"), }, span({ diff --git a/js/tw-styles.css b/js/tw-styles.css index 0d5d8fc..67c848f 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2055,6 +2055,10 @@ details.collapse summary::-webkit-details-marker{ left: 0.5rem; } +.left-4{ + left: 1rem; +} + .right-0{ right: 0px; } @@ -2063,6 +2067,10 @@ details.collapse summary::-webkit-details-marker{ right: 0.5rem; } +.right-4{ + right: 1rem; +} + .top-0{ top: 0px; } @@ -2075,22 +2083,18 @@ details.collapse summary::-webkit-details-marker{ top: 0.5rem; } -.left-4{ - left: 1rem; -} - .top-4{ top: 1rem; } -.right-4{ - right: 1rem; -} - .z-\[1000\]{ z-index: 1000; } +.z-\[1001\]{ + z-index: 1001; +} + .z-\[100\]{ z-index: 100; } @@ -2152,8 +2156,8 @@ details.collapse summary::-webkit-details-marker{ height: 320px; } -.h-\[450px\]{ - height: 450px; +.h-\[394px\]{ + height: 394px; } .h-full{ @@ -2164,12 +2168,8 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } -.h-\[397px\]{ - height: 397px; -} - -.h-\[394px\]{ - height: 394px; +.w-24{ + width: 6rem; } .w-3{ @@ -2205,14 +2205,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-24{ - width: 6rem; -} - -.w-36{ - width: 9rem; -} - .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2315,10 +2307,6 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } -.border-0{ - border-width: 0px; -} - .border-t-4{ border-top-width: 4px; } @@ -2440,6 +2428,11 @@ details.collapse summary::-webkit-details-marker{ padding: 1rem; } +.\!px-0{ + padding-left: 0px !important; + padding-right: 0px !important; +} + .\!py-0{ padding-top: 0px !important; padding-bottom: 0px !important; @@ -2455,15 +2448,6 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } -.\!px-0{ - padding-left: 0px !important; - padding-right: 0px !important; -} - -.pb-24{ - padding-bottom: 6rem; -} - .text-start{ text-align: start; } From 4ae35f1a8533613b6b334c30e0703fa6a02cf6a5 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 9 Nov 2023 12:00:34 -0800 Subject: [PATCH 006/127] default json import --- js/AvatarPreview.js | 115 ++++++++++++++++++------- js/tw-styles.css | 199 ++++++++++++++++++++++++++++++++++++++++++++ routes.py | 8 ++ 3 files changed, 290 insertions(+), 32 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 7e6f916..f1eeb17 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -1,9 +1,35 @@ import { van } from "./van.js"; import { imageUrl, showPreview, previewUrl, showEditor } from "./state.js"; -const { button, iframe, div, img, input, label } = van.tags; +const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; +async function loadJSONWorkflow() { + const json = await (await fetch("./get_default_workflow")).json(); + app.loadGraphData(json); + console.log(json); +} + +function uploadImage() { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const nodes = graph.findNodesByType("LoadImage"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.value == "image").callback(); +} + +const jsonWorkflowLoading = van.state(true); + export function AvatarPreview() { + console.log("getting workflow json now"); + loadJSONWorkflow().then(() => { + console.log("done loading"); + jsonWorkflowLoading.val = false; + }); + return div( { class: () => { @@ -17,24 +43,37 @@ export function AvatarPreview() { }, div( { - class: "absolute top-4 left-4 flex flex-col gap-2", + class: "absolute top-4 left-4 flex flex-row gap-2", }, button( { - class: () => " btn btn-outline w-24 text-xs !px-0 normal-case", + class: () => + "btn flex flex-row btn-ghost text-black normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", onclick: () => { - // console.log(showEditor); showPreview.val = false; }, }, - "X" + span({ + class: "iconify text-lg", + "data-icon": "ic:baseline-arrow-back", + "data-inline": "false", + }), + div("Back") ), button( { - class: () => "btn btn-outline w-24 text-xs !px-0 normal-case", - onclick: () => document.getElementById("comfy-load-button").click(), + class: () => + "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + onclick: () => { + document.getElementById("comfy-load-button").click(); + }, }, - "Load Shape flow" + span({ + class: "iconify text-lg", + "data-icon": "ic:round-swap-vert", + "data-inline": "false", + }), + () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") ) ), button( @@ -53,7 +92,7 @@ export function AvatarPreview() { }, div( { - class: () => "flex flex-col bg-white justify-start h-full", + class: () => "flex flex-col bg-white justify-center h-full", }, div( { @@ -69,54 +108,66 @@ export function AvatarPreview() { }, "Get your DALLE3 AI Personal Clone" ), - label( + // input({ + // type: "file", + // class: "file-input file-input-bordered w-full w-full", + // }), + button( { class: () => - " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", - // for: "imgtosam", + "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", onclick: () => { - /** @type {import('../../../web/types/litegraph.js').LGraph}*/ - const graph = app.graph; - const nodes = graph.findNodesByType("LoadImage"); - - /** @type {any[]}*/ - const widgets = nodes[0].widgets - console.log(nodes[0]); - console.log(nodes[0].widgets); - widgets.find(x => x.value == "image").callback() + uploadImage(); }, }, - div( - { - class: () => " absolute flex justify-center items-center ", - }, - "upload your image" - ), - img({ - class: () => " z-[10] object-contain w-[550px] h-[394px]", - id: "imgUrl", + div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", }) ), + // label( + // { + // class: () => + // " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", + // // for: "imgtosam", + // onclick: () => {}, + // }, + // div( + // { + // class: () => " absolute flex justify-center items-center ", + // }, + // "upload your image" + // ), + // img({ + // class: () => " z-[10] object-contain w-[550px] h-[394px]", + // id: "imgUrl", + // }) + // ), div( { class: () => " w-full flex flex-col justify-center items-center", }, button( { - class: () => "btn btn-outline mt-24 w-96 normal-case", + class: () => "btn mt-24 w-96 normal-case", onclick: () => { document.getElementById("sam").click(); }, }, + div({ class: "badge badge-neutral" }, "2"), "Edit segment" ), button( { - class: () => "btn btn-outline mt-4 w-96 normal-case", + class: () => "btn mt-4 w-96 normal-case", onclick: () => { document.getElementById("queue-button").click(); }, }, + div({ class: "badge badge-neutral" }, "3"), "Generate" ) ) diff --git a/js/tw-styles.css b/js/tw-styles.css index 67c848f..e91789f 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -608,6 +608,33 @@ html{ justify-content: center; } +.badge{ + display: inline-flex; + align-items: center; + justify-content: center; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + height: 1.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + width: -moz-fit-content; + width: fit-content; + padding-left: 0.563rem; + padding-right: 0.563rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + border-radius: var(--rounded-badge, 1.9rem); +} + @media (hover:hover){ .label a:hover{ --tw-text-opacity: 1; @@ -820,6 +847,20 @@ html{ background-color: hsl(var(--b3) / var(--tw-bg-opacity)); } + .btn-primary:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + } + + .btn-neutral:hover{ + --tw-border-opacity: 1; + border-color: hsl(var(--nf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--nf) / var(--tw-bg-opacity)); + } + .btn.glass:hover{ --glass-opacity: 25%; --glass-border-opacity: 15%; @@ -940,6 +981,62 @@ html{ display: none; } +.file-input{ + height: 3rem; + flex-shrink: 1; + padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + overflow: hidden; + border-width: 1px; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0; + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + border-radius: var(--rounded-btn, 0.5rem); +} + +.file-input::file-selector-button{ + margin-right: 1rem; + display: inline-flex; + height: 2.875rem; + min-height: 2.875rem; + flex-shrink: 0; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 1em; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + border-style: solid; + --tw-border-opacity: 1; + border-color: hsl(var(--n) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + font-weight: 600; + text-transform: uppercase; + text-transform: var(--btn-text-case, uppercase); + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); + text-decoration-line: none; + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; +} + .label{ display: flex; -webkit-user-select: none; @@ -1135,6 +1232,20 @@ html{ border-color: hsl(var(--b1) / var(--tw-border-opacity)); } +.badge-neutral{ + --tw-border-opacity: 1; + border-color: hsl(var(--n) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); +} + +.badge-outline.badge-neutral{ + --tw-text-opacity: 1; + color: hsl(var(--n) / var(--tw-text-opacity)); +} + .btm-nav > *:where(.active){ border-top-width: 2px; --tw-bg-opacity: 1; @@ -1158,6 +1269,40 @@ html{ outline-offset: 2px; } +.btn-primary{ + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); + outline-color: hsl(var(--p) / 1); +} + +.btn-primary.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); +} + +.btn-neutral{ + --tw-border-opacity: 1; + border-color: hsl(var(--n) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); + outline-color: hsl(var(--n) / 1); +} + +.btn-neutral.btn-active{ + --tw-border-opacity: 1; + border-color: hsl(var(--nf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--nf) / var(--tw-bg-opacity)); +} + .btn.glass{ --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; @@ -1488,6 +1633,46 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.file-input-bordered{ + --tw-border-opacity: 0.2; +} + +.file-input:focus{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: hsl(var(--bc) / 0.2); +} + +.file-input-disabled, + .file-input[disabled]{ + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + --tw-text-opacity: 0.2; +} + +.file-input-disabled::-moz-placeholder, .file-input[disabled]::-moz-placeholder{ + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.file-input-disabled::placeholder, + .file-input[disabled]::placeholder{ + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.file-input-disabled::file-selector-button, .file-input[disabled]::file-selector-button{ + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.2; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -2205,6 +2390,10 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.max-w-xs{ + max-width: 20rem; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2708,6 +2897,16 @@ img[src] { background-color: hsl(var(--nf) / var(--tw-bg-opacity)); } +.hover\:bg-stone-900:hover{ + --tw-bg-opacity: 1; + background-color: rgb(28 25 23 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-800:hover{ + --tw-bg-opacity: 1; + background-color: rgb(41 37 36 / var(--tw-bg-opacity)); +} + .hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); diff --git a/routes.py b/routes.py index bd86e0d..6405615 100644 --- a/routes.py +++ b/routes.py @@ -90,3 +90,11 @@ async def post_sam_model(request): f, ) return web.json_response({}) + + +@server.PromptServer.instance.routes.get("/get_default_workflow") +async def get_default_workflow(request): + json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" + response = requests.get(json_link) + response.raise_for_status() + return web.json_response(response.json()) \ No newline at end of file From ea6a5a7af66aed153a56f81ec21d097d6b759a00 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 9 Nov 2023 12:19:15 -0800 Subject: [PATCH 007/127] update UI --- js/AvatarPreview.js | 17 ++++++++++++----- js/LayerEditor.js | 2 +- js/state.js | 2 +- js/tw-styles.css | 12 ++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index f1eeb17..a1305bc 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -18,7 +18,7 @@ function uploadImage() { const widgets = nodes[0].widgets; console.log(nodes[0]); console.log(nodes[0].widgets); - widgets.find((x) => x.value == "image").callback(); + widgets.find((x) => x.type == "button").callback(); } const jsonWorkflowLoading = van.state(true); @@ -55,10 +55,9 @@ export function AvatarPreview() { }, span({ class: "iconify text-lg", - "data-icon": "ic:baseline-arrow-back", + "data-icon": "ic:round-close", "data-inline": "false", }), - div("Back") ), button( { @@ -154,7 +153,15 @@ export function AvatarPreview() { { class: () => "btn mt-24 w-96 normal-case", onclick: () => { - document.getElementById("sam").click(); + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const nodes = graph.findNodesByType("SAM MultiLayer"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.type == "button").callback(); }, }, div({ class: "badge badge-neutral" }, "2"), @@ -178,7 +185,7 @@ export function AvatarPreview() { name: "avatech-viewer-iframe", allow: "cross-origin-isolated", class: () => - "w-[370px] h-[320px] z-[100] pointer-events-auto flex border-none " + + "w-[401px] h-[400px] z-[100] pointer-events-auto flex border-none " + (showPreview.val ? "" : "hidden"), // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 607fd65..e570e9c 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -120,7 +120,7 @@ export function LayerEditor() { button( { class: () => - "btn btn-circle flex flex-row btn-ghost normal-case absolute p-0 rounded-md left-2 top-0 z-[200] w-fit", + "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-2 top-0 z-[200] w-fit", onclick: () => { console.log("close"); showImageEditor.val = false; diff --git a/js/state.js b/js/state.js index 011b781..2af2d88 100644 --- a/js/state.js +++ b/js/state.js @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=300&height=300&hideTrigger=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=400&height=400&hideTrigger=true"); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); export const fileName = van.state(''); diff --git a/js/tw-styles.css b/js/tw-styles.css index e91789f..0c3681d 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2353,6 +2353,10 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } +.h-\[400px\]{ + height: 400px; +} + .w-24{ width: 6rem; } @@ -2390,6 +2394,14 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-\[400px\]{ + width: 400px; +} + +.w-\[401px\]{ + width: 401px; +} + .max-w-xs{ max-width: 20rem; } From 0dbf97425414698b3e3737346f7d5fd6e9eabd13 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 10 Nov 2023 17:32:21 +0800 Subject: [PATCH 008/127] fix: inconsistent segments --- js/LayerEditor.js | 59 +++++++++++++++-------- js/index.js | 2 + js/state.js | 1 + routes.py | 28 ++++++++++- sam/sam_multilayer.py | 107 +++++++++++++++++++++++------------------- 5 files changed, 129 insertions(+), 68 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index e570e9c..e5b561c 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -1,4 +1,5 @@ import { SideBar } from "./SideBar.js"; +import { api } from "./api.js"; import { runONNX } from "./onnx.js"; import { showImageEditor, @@ -11,6 +12,7 @@ import { selectedLayer, imagePromptsMulti, embeddings, + embeddingID, } from "./state.js"; import { van } from "./van.js"; const { button, div, img, canvas, span } = van.tags; @@ -26,6 +28,18 @@ export function updateImagePrompts() { targetNode.val.widgets.find((x) => x.name === "image_prompts_json").value = JSON.stringify(imagePromptsMulti.val); + + const canvas = document.getElementById("mask-canvas"); + const base64Image = canvas.toDataURL(); + api.fetchApi("/segments", { + method: "POST", + body: JSON.stringify({ + name: embeddingID.val, + segments: { + [selectedLayer.val]: base64Image, + }, + }), + }); } else { targetNode.val.widgets.find((x) => x.name === "image_prompts_json").value = JSON.stringify(imagePrompts.val); @@ -33,7 +47,7 @@ export function updateImagePrompts() { targetNode.val.graph.change(); } -function handleClick(e) { +async function handleClick(e) { const rect = e.target.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; @@ -49,18 +63,17 @@ function handleClick(e) { ...imagePrompts.val, { x: relativeX, y: relativeY, label: e.isRight ? 0 : 1 }, ]; - + await drawSegment(getClicks()); updateImagePrompts(); - drawSegment(getClicks()); } -function handlePointClick(e, point) { +async function handlePointClick(e, point) { e.preventDefault(); imagePrompts.val = imagePrompts.val.filter( (x) => !(x.x === point.x && x.y === point.y) ); + await drawSegment(getClicks()); updateImagePrompts(); - drawSegment(getClicks()); } function handleImageSize(image) { @@ -82,7 +95,7 @@ export function getClicks() { })); } -export function drawSegment(clicks) { +export async function drawSegment(clicks) { const canvas = document.getElementById("mask-canvas"); const ctx = canvas.getContext("2d"); if (clicks.length === 0) { @@ -90,12 +103,11 @@ export function drawSegment(clicks) { return; } if (embeddings.val) { - runONNX(clicks, embeddings.val).then((mask) => { - if (mask) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(mask, 0, 0); - } - }); + const mask = await runONNX(clicks, embeddings.val); + if (mask) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(mask, 0, 0); + } } } @@ -124,6 +136,13 @@ export function LayerEditor() { onclick: () => { console.log("close"); showImageEditor.val = false; + api.fetchApi("/segments_order", { + method: "POST", + body: JSON.stringify({ + name: embeddingID.val, + order: Object.keys(imagePromptsMulti.val), + }), + }); }, }, span({ @@ -181,13 +200,13 @@ export function LayerEditor() { canvas.width = e.target.naturalWidth; canvas.height = e.target.naturalHeight; }, - oncontextmenu: (e) => { + oncontextmenu: async (e) => { e.preventDefault(); e.isRight = true; - handleClick(e); + await handleClick(e); }, - onclick: (e) => { - handleClick(e); + onclick: async (e) => { + await handleClick(e); }, onmouseleave: (e) => { drawSegment(getClicks()); @@ -247,11 +266,11 @@ export function LayerEditor() { point.label === 1 ? "bg-green-500" : "bg-red-500" }`, - oncontextmenu: (e) => { - handlePointClick(e, point); + oncontextmenu: async (e) => { + await handlePointClick(e, point); }, - onclick: (e) => { - handlePointClick(e, point); + onclick: async (e) => { + await handlePointClick(e, point); }, }); }) diff --git a/js/index.js b/js/index.js index ba592c2..975ae4d 100644 --- a/js/index.js +++ b/js/index.js @@ -15,6 +15,7 @@ import { showPreview, shareLoading, previewModelId, + embeddingID, } from "./state.js"; import { van } from "./van.js"; import { app } from "./app.js"; @@ -273,6 +274,7 @@ function showMyImageEditor(node) { if (split.length > 1) id = split[1]; node.widgets.find((x) => x.name === "embedding_id").value = id; + embeddingID.val = id; api .fetchApi("/sam_model", { diff --git a/js/state.js b/js/state.js index 2af2d88..8d68713 100644 --- a/js/state.js +++ b/js/state.js @@ -53,3 +53,4 @@ export const targetNode = van.state(); export const imageSize = van.state({ width: 0, height: 0, samScale: 0 }); export const embeddings = van.state(); +export const embeddingID = van.state("Test"); diff --git a/routes.py b/routes.py index 6405615..1160502 100644 --- a/routes.py +++ b/routes.py @@ -8,6 +8,7 @@ import json import numpy as np import server import re +import base64 # For speeding up ONNX model, see https://github.com/facebookresearch/segment-anything/tree/main/demo#onnx-multithreading-with-sharedarraybuffer def inject_headers(original_handler): @@ -97,4 +98,29 @@ async def get_default_workflow(request): json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" response = requests.get(json_link) response.raise_for_status() - return web.json_response(response.json()) \ No newline at end of file + return web.json_response(response.json()) + + +@server.PromptServer.instance.routes.post("/segments") +async def post_segments(request): + post = await request.json() + name = post.get("name") + segments = post.get("segments") + os.makedirs(os.path.join(folder_paths.base_path, f"output/{name}"), exist_ok=True) + for key, value in segments.items(): + filename = os.path.join(folder_paths.base_path, f"output/{name}/{key}.png") + with open(filename, "wb") as f: + f.write(base64.b64decode(value.split(",")[1])) + return web.json_response({}) + + +@server.PromptServer.instance.routes.post("/segments_order") +async def post_segments(request): + post = await request.json() + name = post.get("name") + order = post.get("order") + output_dir = os.path.join(folder_paths.base_path, f"output/{name}") + os.makedirs(output_dir, exist_ok=True) + with open(os.path.join(output_dir, "order.json") , "w") as f: + json.dump(order, f) + return web.json_response({}) diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index a3da63a..35a80a6 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -6,6 +6,7 @@ import re import json from segment_anything import sam_model_registry, SamPredictor from einops import rearrange, repeat +from PIL import Image global_predictor = None @@ -42,63 +43,75 @@ class SAMMultiLayer: FUNCTION = "load_image" def load_image(self, image, ckpt, embedding_id, image_prompts_json): - global global_predictor - model_type = re.findall(r'vit_[lbh]', ckpt)[0] + # global global_predictor + # model_type = re.findall(r'vit_[lbh]', ckpt)[0] - if global_predictor is None: - ckpt = folder_paths.get_full_path("sams", ckpt) - sam = sam_model_registry[model_type](checkpoint=ckpt) - predictor = SamPredictor(sam) - global_predictor = predictor + # if global_predictor is None: + # ckpt = folder_paths.get_full_path("sams", ckpt) + # sam = sam_model_registry[model_type](checkpoint=ckpt) + # predictor = SamPredictor(sam) + # global_predictor = predictor - predictor = global_predictor + # predictor = global_predictor - emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" - if not os.path.exists(emb_filename): - image_np = (image[0].numpy() * 255).astype(np.uint8) - predictor.set_image(image_np) - emb = predictor.get_image_embedding().cpu().numpy() - np.save(emb_filename, emb) + # emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" + # if not os.path.exists(emb_filename): + # image_np = (image[0].numpy() * 255).astype(np.uint8) + # predictor.set_image(image_np) + # emb = predictor.get_image_embedding().cpu().numpy() + # np.save(emb_filename, emb) - with open(f"{self.output_dir}/{embedding_id}_{model_type}.json", "w") as f: - data = { - "input_size": predictor.input_size, - "original_size": predictor.original_size, - } - json.dump(data, f) - else: - emb = np.load(emb_filename) + # with open(f"{self.output_dir}/{embedding_id}_{model_type}.json", "w") as f: + # data = { + # "input_size": predictor.input_size, + # "original_size": predictor.original_size, + # } + # json.dump(data, f) + # else: + # emb = np.load(emb_filename) - with open(f"{self.output_dir}/{embedding_id}_{model_type}.json") as f: - data = json.load(f) - predictor.input_size = data["input_size"] - predictor.features = torch.from_numpy(emb) - predictor.is_image_set = True - predictor.original_size = data["original_size"] + # with open(f"{self.output_dir}/{embedding_id}_{model_type}.json") as f: + # data = json.load(f) + # predictor.input_size = data["input_size"] + # predictor.features = torch.from_numpy(emb) + # predictor.is_image_set = True + # predictor.original_size = data["original_size"] + # image_prompts = json.loads(image_prompts_json) + + # result = [image_prompts] + + # if isinstance(image_prompts, list): + # pass + # elif all(isinstance(item, list) for item in image_prompts.values()): + # for item in image_prompts.values(): + # if (len(item) == 0): + # h, w, c = image[0].shape + # result.append(torch.zeros(1, h, w, c)) + # continue + # point_coords = np.array([[p['x'], p['y']] for p in item]) + # point_labels = np.array([p['label'] for p in item]) + + # masks, _, _ = predictor.predict( + # point_coords=point_coords, + # point_labels=point_labels, + # ) + # masks = torch.from_numpy(masks) + # masks = rearrange(masks[0], 'h w -> 1 h w') + # out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image + # result.append(out_image) + image_prompts = json.loads(image_prompts_json) - result = [image_prompts] - if isinstance(image_prompts, list): - pass - elif all(isinstance(item, list) for item in image_prompts.values()): - for item in image_prompts.values(): - if (len(item) == 0): - h, w, c = image[0].shape - result.append(torch.zeros(1, h, w, c)) - continue - point_coords = np.array([[p['x'], p['y']] for p in item]) - point_labels = np.array([p['label'] for p in item]) + with open(f"{self.output_dir}/{embedding_id}/order.json") as f: + order = json.load(f) - masks, _, _ = predictor.predict( - point_coords=point_coords, - point_labels=point_labels, - ) - masks = torch.from_numpy(masks) - masks = rearrange(masks[0], 'h w -> 1 h w') - out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image - result.append(out_image) + for segment in order: + image = Image.open(f"{self.output_dir}/{embedding_id}/{segment}.png") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + result.append(image) return result From 6f0f2e2c30349fcbaf69d493cee1c98eae7fae39 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 10 Nov 2023 17:58:25 +0800 Subject: [PATCH 009/127] fix: segment order json --- js/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/index.js b/js/index.js index 975ae4d..0ef1789 100644 --- a/js/index.js +++ b/js/index.js @@ -461,6 +461,13 @@ const ext = { } else { showImageEditor.val = false; showEditor.val = false; + api.fetchApi("/segments_order", { + method: "POST", + body: JSON.stringify({ + name: embeddingID.val, + order: Object.keys(imagePromptsMulti.val), + }), + }); } } }, From eed3053b2e805454fec9c1aa4297f6f733b9c9a1 Mon Sep 17 00:00:00 2001 From: San Date: Fri, 10 Nov 2023 19:47:57 +0800 Subject: [PATCH 010/127] get avatar link button --- js/AvatarPreview.js | 99 ++++++++++++++++++++++++++-------- js/index.js | 2 +- js/tw-styles.css | 126 +++++++++++++++++++++++--------------------- requirements.txt | 1 + routes.py | 8 +++ 5 files changed, 152 insertions(+), 84 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index a1305bc..0e9946f 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -2,6 +2,7 @@ import { van } from "./van.js"; import { imageUrl, showPreview, previewUrl, showEditor } from "./state.js"; const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; +import { uploadPreview } from './index.js'; async function loadJSONWorkflow() { const json = await (await fetch("./get_default_workflow")).json(); @@ -30,6 +31,8 @@ export function AvatarPreview() { jsonWorkflowLoading.val = false; }); + const email = van.state(""); + return div( { class: () => { @@ -72,8 +75,8 @@ export function AvatarPreview() { "data-icon": "ic:round-swap-vert", "data-inline": "false", }), - () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") - ) + () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow"), + ), ), button( { @@ -81,7 +84,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter" + "Twitter", ), div( { @@ -98,14 +101,14 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1" + "Avatech v1", ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone" + "Get your DALLE3 AI Personal Clone", ), // input({ // type: "file", @@ -125,7 +128,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "material-symbols:drive-folder-upload", "data-inline": "false", - }) + }), ), // label( // { @@ -155,6 +158,9 @@ export function AvatarPreview() { onclick: () => { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if(!imageNodes[0].imgs) return + const nodes = graph.findNodesByType("SAM MultiLayer"); /** @type {any[]}*/ @@ -165,32 +171,79 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit segment" + "Edit segment", ), button( { class: () => "btn mt-4 w-96 normal-case", onclick: () => { + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if(!imageNodes[0].imgs) return document.getElementById("queue-button").click(); }, }, div({ class: "badge badge-neutral" }, "3"), - "Generate" - ) - ) + "Generate", + ), + ), ), - iframe({ - id: "avatech-viewer-iframe", - title: "avatech-viewer-iframe", - name: "avatech-viewer-iframe", - allow: "cross-origin-isolated", - class: () => - "w-[401px] h-[400px] z-[100] pointer-events-auto flex border-none " + - (showPreview.val ? "" : "hidden"), - // src: "https://labs.avatech.ai/viewer/default", - // src: "http://localhost:3000/viewer/default", - src: previewUrl, - }) - ) + div( + { class: () => "flex flex-col" }, + iframe({ + id: "avatech-viewer-iframe", + title: "avatech-viewer-iframe", + name: "avatech-viewer-iframe", + allow: "cross-origin-isolated", + class: () => + "w-[401px] h-[400px] z-[100] pointer-events-auto flex border-none " + + (showPreview.val ? "" : "hidden"), + // src: "https://labs.avatech.ai/viewer/default", + // src: "http://localhost:3000/viewer/default", + src: previewUrl, + }), + div( + { class: () => "w-full flex flex-col gap-2 justify-center mt-24" }, + div( + { class: () => "flex text-black font-bold" }, + div({}, "We are launching OpenAI Assistant API integration soon"), + ), + div( + { class: () => "flex justify-center" }, + input({ + type: "text", + class: () => + "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 text-xl", + onchange: (e) => { + email.val = e.target.value; + }, + }), + button( + { + class: () => + "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", + onclick: async () => { + const url = await (await fetch("./get_webhook")).json(); + await uploadPreview() + await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username: "Avabot", + avatar_url: + "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", + content: "New register! \n" + email.val, + }), + }); + }, + }, + "Get Avatar Link", + ), + ), + ), + ), + ), ); } diff --git a/js/index.js b/js/index.js index 0ef1789..9d5cca9 100644 --- a/js/index.js +++ b/js/index.js @@ -634,7 +634,7 @@ const ext = { }, }; -async function uploadPreview() { +export async function uploadPreview() { if (fileName.val == "") app.ui.dialog.show("Please create your avatar first."); else { diff --git a/js/tw-styles.css b/js/tw-styles.css index 0c3681d..fd067f2 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -840,6 +840,16 @@ html{ opacity: 1; } + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover{ + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } + .btn:hover{ --tw-border-opacity: 1; border-color: hsl(var(--b3) / var(--tw-border-opacity)); @@ -847,13 +857,6 @@ html{ background-color: hsl(var(--b3) / var(--tw-bg-opacity)); } - .btn-primary:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--pf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--pf) / var(--tw-bg-opacity)); - } - .btn-neutral:hover{ --tw-border-opacity: 1; border-color: hsl(var(--nf) / var(--tw-border-opacity)); @@ -1252,6 +1255,16 @@ html{ background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } +.btm-nav > *.disabled, + .btm-nav > *[disabled]{ + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + .btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; @@ -1269,23 +1282,6 @@ html{ outline-offset: 2px; } -.btn-primary{ - --tw-border-opacity: 1; - border-color: hsl(var(--p) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--p) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--pc) / var(--tw-text-opacity)); - outline-color: hsl(var(--p) / 1); -} - -.btn-primary.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--pf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--pf) / var(--tw-bg-opacity)); -} - .btn-neutral{ --tw-border-opacity: 1; border-color: hsl(var(--n) / var(--tw-border-opacity)); @@ -2333,18 +2329,22 @@ details.collapse summary::-webkit-details-marker{ aspect-ratio: 1 / 1; } +.h-12{ + height: 3rem; +} + .h-3{ height: 0.75rem; } -.h-\[320px\]{ - height: 320px; -} - .h-\[394px\]{ height: 394px; } +.h-\[400px\]{ + height: 400px; +} + .h-full{ height: 100%; } @@ -2353,10 +2353,6 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } -.h-\[400px\]{ - height: 400px; -} - .w-24{ width: 6rem; } @@ -2377,8 +2373,8 @@ details.collapse summary::-webkit-details-marker{ width: 24rem; } -.w-\[370px\]{ - width: 370px; +.w-\[401px\]{ + width: 401px; } .w-\[550px\]{ @@ -2394,18 +2390,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-\[400px\]{ - width: 400px; -} - -.w-\[401px\]{ - width: 401px; -} - -.max-w-xs{ - max-width: 20rem; -} - .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2440,10 +2424,6 @@ details.collapse summary::-webkit-details-marker{ align-items: center; } -.justify-start{ - justify-content: flex-start; -} - .justify-center{ justify-content: center; } @@ -2488,6 +2468,21 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.125rem; } +.\!rounded-l-none{ + border-top-left-radius: 0px !important; + border-bottom-left-radius: 0px !important; +} + +.\!rounded-r-lg{ + border-top-right-radius: 0.5rem !important; + border-bottom-right-radius: 0.5rem !important; +} + +.rounded-l-lg{ + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + .rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -2508,6 +2503,14 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } +.border{ + border-width: 1px; +} + +.\!border-r-0{ + border-right-width: 0px !important; +} + .border-t-4{ border-top-width: 4px; } @@ -2516,6 +2519,11 @@ details.collapse summary::-webkit-details-marker{ border-style: none; } +.border-black{ + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + .border-orange-500{ --tw-border-opacity: 1; border-color: rgb(249 115 22 / var(--tw-border-opacity)); @@ -2617,6 +2625,10 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.p-1{ + padding: 0.25rem; +} + .p-2{ padding: 0.5rem; } @@ -2734,6 +2746,10 @@ details.collapse summary::-webkit-details-marker{ --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); } +.ring-black\/30{ + --tw-ring-color: rgb(0 0 0 / 0.3); +} + .filter{ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -2909,16 +2925,6 @@ img[src] { background-color: hsl(var(--nf) / var(--tw-bg-opacity)); } -.hover\:bg-stone-900:hover{ - --tw-bg-opacity: 1; - background-color: rgb(28 25 23 / var(--tw-bg-opacity)); -} - -.hover\:bg-stone-800:hover{ - --tw-bg-opacity: 1; - background-color: rgb(41 37 36 / var(--tw-bg-opacity)); -} - .hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); diff --git a/requirements.txt b/requirements.txt index 0ea6151..98e4f81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ einops bpy segment-anything tqdm +python-dotenv # -e git+https://github.com/facebookresearch/segment-anything.git#egg=segment_anything \ No newline at end of file diff --git a/routes.py b/routes.py index 1160502..9ef237c 100644 --- a/routes.py +++ b/routes.py @@ -1,6 +1,7 @@ from aiohttp import web from segment_anything import sam_model_registry, SamPredictor from PIL import Image, ImageOps +from dotenv import load_dotenv import os import requests import folder_paths @@ -10,6 +11,8 @@ import server import re import base64 +load_dotenv() + # For speeding up ONNX model, see https://github.com/facebookresearch/segment-anything/tree/main/demo#onnx-multithreading-with-sharedarraybuffer def inject_headers(original_handler): async def _handler(request): @@ -124,3 +127,8 @@ async def post_segments(request): with open(os.path.join(output_dir, "order.json") , "w") as f: json.dump(order, f) return web.json_response({}) + +@server.PromptServer.instance.routes.get("/get_webhook") +async def get_webhook(request): + url = os.getenv('DISCORD_WEBHOOK_URL') + return web.json_response(url) From ef86167448a2d70df315f40f9dbb52863eaf1bbf Mon Sep 17 00:00:00 2001 From: San Date: Fri, 10 Nov 2023 20:11:19 +0800 Subject: [PATCH 011/127] z index tweak --- js/AvatarPreview.js | 2 +- js/tw-styles.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 0e9946f..ba0e84a 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -40,7 +40,7 @@ export function AvatarPreview() { return ( (showPreview.val ? "" : "hidden ") + - "w-full h-screen absolute left-0 top-0 z-[100] pointer-events-auto flex border-none " + "w-full h-screen absolute left-0 top-0 z-[99] pointer-events-auto flex border-none " ); }, }, diff --git a/js/tw-styles.css b/js/tw-styles.css index fd067f2..179c11d 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2288,6 +2288,10 @@ details.collapse summary::-webkit-details-marker{ z-index: 200; } +.z-\[99\]{ + z-index: 99; +} + .my-0{ margin-top: 0px; margin-bottom: 0px; From 0e3dce53540139ee0c45490d1988c4689c07b614 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 10 Nov 2023 07:18:40 -0500 Subject: [PATCH 012/127] chore: preview style --- js/AvatarPreview.js | 3 ++- js/tw-styles.css | 63 +++++++++++++++++++-------------------------- routes.py | 3 ++- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index ba0e84a..1f43a37 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -196,7 +196,7 @@ export function AvatarPreview() { name: "avatech-viewer-iframe", allow: "cross-origin-isolated", class: () => - "w-[401px] h-[400px] z-[100] pointer-events-auto flex border-none " + + "w-[450px] h-[450px] z-[100] pointer-events-auto flex border-none overflow-hidden" + (showPreview.val ? "" : "hidden"), // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", @@ -217,6 +217,7 @@ export function AvatarPreview() { onchange: (e) => { email.val = e.target.value; }, + placeholder: "Enter your email", }), button( { diff --git a/js/tw-styles.css b/js/tw-styles.css index 179c11d..c14b1ad 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:focus{ +.dropdown > *:not(summary):focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -840,16 +840,6 @@ html{ opacity: 1; } - .btm-nav > *.disabled:hover, - .btm-nav > *[disabled]:hover{ - pointer-events: none; - --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - --tw-text-opacity: 0.2; - } - .btn:hover{ --tw-border-opacity: 1; border-color: hsl(var(--b3) / var(--tw-border-opacity)); @@ -1003,8 +993,7 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 2.875rem; - min-height: 2.875rem; + height: 100%; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -1082,8 +1071,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; + border-start-end-radius: inherit; + border-end-end-radius: inherit; } .link{ @@ -1255,16 +1244,6 @@ html{ background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } -.btm-nav > *.disabled, - .btm-nav > *[disabled]{ - pointer-events: none; - --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - --tw-text-opacity: 0.2; -} - .btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; @@ -1669,6 +1648,11 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } +.input input:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1677,7 +1661,8 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus{ +.input:focus, + .input:focus-within{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1750,9 +1735,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; + bottom: 0.75rem; left: 0px; top: 0.75rem; - bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1849,8 +1834,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - top: 50%; left: 0.5rem; + top: 50%; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1864,8 +1849,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - top: 50%; left: 1.25rem; + top: 50%; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -2095,8 +2080,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2118,8 +2103,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2134,8 +2119,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-top: -1px; margin-left: -0px; + margin-top: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2345,8 +2330,8 @@ details.collapse summary::-webkit-details-marker{ height: 394px; } -.h-\[400px\]{ - height: 400px; +.h-\[450px\]{ + height: 450px; } .h-full{ @@ -2377,8 +2362,8 @@ details.collapse summary::-webkit-details-marker{ width: 24rem; } -.w-\[401px\]{ - width: 401px; +.w-\[450px\]{ + width: 450px; } .w-\[550px\]{ @@ -2452,6 +2437,10 @@ details.collapse summary::-webkit-details-marker{ place-self: end; } +.overflow-hidden{ + overflow: hidden; +} + .rounded{ border-radius: 0.25rem; } diff --git a/routes.py b/routes.py index 9ef237c..7f0049d 100644 --- a/routes.py +++ b/routes.py @@ -98,7 +98,8 @@ async def post_sam_model(request): @server.PromptServer.instance.routes.get("/get_default_workflow") async def get_default_workflow(request): - json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" + # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" + json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" response = requests.get(json_link) response.raise_for_status() return web.json_response(response.json()) From eb6b2eea6c64a174f5dd47326495225cae72f3b3 Mon Sep 17 00:00:00 2001 From: San Date: Fri, 10 Nov 2023 20:37:14 +0800 Subject: [PATCH 013/127] loading state --- js/AvatarPreview.js | 26 +++++++++++--- js/tw-styles.css | 83 +++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 1f43a37..1d5849b 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -2,7 +2,8 @@ import { van } from "./van.js"; import { imageUrl, showPreview, previewUrl, showEditor } from "./state.js"; const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; -import { uploadPreview } from './index.js'; +import { uploadPreview } from "./index.js"; +import { api } from "./api.js"; async function loadJSONWorkflow() { const json = await (await fetch("./get_default_workflow")).json(); @@ -31,6 +32,16 @@ export function AvatarPreview() { jsonWorkflowLoading.val = false; }); + const loading = van.state(false); + + api.addEventListener("execution_start", (evt) => { + loading.val = true; + }); + + api.addEventListener("executed", (evt) => { + loading.val = false; + }); + const email = van.state(""); return div( @@ -159,7 +170,7 @@ export function AvatarPreview() { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); - if(!imageNodes[0].imgs) return + if (!imageNodes[0].imgs) return; const nodes = graph.findNodesByType("SAM MultiLayer"); @@ -179,12 +190,17 @@ export function AvatarPreview() { onclick: () => { const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); - if(!imageNodes[0].imgs) return + if (!imageNodes[0].imgs) return; document.getElementById("queue-button").click(); }, }, div({ class: "badge badge-neutral" }, "3"), - "Generate", + () => + loading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Generate", ), ), ), @@ -225,7 +241,7 @@ export function AvatarPreview() { "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", onclick: async () => { const url = await (await fetch("./get_webhook")).json(); - await uploadPreview() + await uploadPreview(); await fetch(url, { method: "POST", headers: { diff --git a/js/tw-styles.css b/js/tw-styles.css index c14b1ad..25ee0be 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:not(summary):focus{ +.dropdown > *:focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -840,6 +840,16 @@ html{ opacity: 1; } + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover{ + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } + .btn:hover{ --tw-border-opacity: 1; border-color: hsl(var(--b3) / var(--tw-border-opacity)); @@ -993,7 +1003,8 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 100%; + height: 2.875rem; + min-height: 2.875rem; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -1071,8 +1082,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-start-end-radius: inherit; - border-end-end-radius: inherit; + border-top-right-radius: inherit; + border-bottom-right-radius: inherit; } .link{ @@ -1244,6 +1255,16 @@ html{ background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } +.btm-nav > *.disabled, + .btm-nav > *[disabled]{ + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + .btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; @@ -1648,11 +1669,6 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } -.input input:focus{ - outline: 2px solid transparent; - outline-offset: 2px; -} - .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1661,8 +1677,7 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus, - .input:focus-within{ +.input:focus{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1718,6 +1733,22 @@ details.collapse summary::-webkit-details-marker{ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } +.\!loading{ + pointer-events: none !important; + display: inline-block !important; + aspect-ratio: 1 / 1 !important; + width: 1.5rem !important; + background-color: currentColor !important; + -webkit-mask-size: 100% !important; + mask-size: 100% !important; + -webkit-mask-repeat: no-repeat !important; + mask-repeat: no-repeat !important; + -webkit-mask-position: center !important; + mask-position: center !important; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E") !important; + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E") !important; +} + .loading-spinner{ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); @@ -1735,9 +1766,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; - bottom: 0.75rem; left: 0px; top: 0.75rem; + bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1834,8 +1865,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - left: 0.5rem; top: 50%; + left: 0.5rem; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1849,8 +1880,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - left: 1.25rem; top: 50%; + left: 1.25rem; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -2080,8 +2111,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2103,8 +2134,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2119,8 +2150,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-left: -0px; margin-top: -1px; + margin-left: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2330,8 +2361,8 @@ details.collapse summary::-webkit-details-marker{ height: 394px; } -.h-\[450px\]{ - height: 450px; +.h-\[400px\]{ + height: 400px; } .h-full{ @@ -2342,6 +2373,10 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } +.h-\[450px\]{ + height: 450px; +} + .w-24{ width: 6rem; } @@ -2362,8 +2397,8 @@ details.collapse summary::-webkit-details-marker{ width: 24rem; } -.w-\[450px\]{ - width: 450px; +.w-\[401px\]{ + width: 401px; } .w-\[550px\]{ @@ -2379,6 +2414,10 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-\[450px\]{ + width: 450px; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); From 0810567fa64f0ff78aaf8dd638392a2276832aae Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 10 Nov 2023 23:12:02 +0800 Subject: [PATCH 014/127] fix: z index loading status --- js/AvatarPreview.js | 44 ++++++++++++++++++++++++++------------------ js/dialog.js | 2 ++ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 1d5849b..13f7da9 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -33,6 +33,7 @@ export function AvatarPreview() { }); const loading = van.state(false); + const shareLoading = van.state(false); api.addEventListener("execution_start", (evt) => { loading.val = true; @@ -71,7 +72,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "ic:round-close", "data-inline": "false", - }), + }) ), button( { @@ -86,8 +87,8 @@ export function AvatarPreview() { "data-icon": "ic:round-swap-vert", "data-inline": "false", }), - () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow"), - ), + () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") + ) ), button( { @@ -95,7 +96,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter", + "Twitter" ), div( { @@ -112,14 +113,14 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1", + "Avatech v1" ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone", + "Get your DALLE3 AI Personal Clone" ), // input({ // type: "file", @@ -139,7 +140,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "material-symbols:drive-folder-upload", "data-inline": "false", - }), + }) ), // label( // { @@ -182,7 +183,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit segment", + "Edit segment" ), button( { @@ -200,9 +201,9 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-lg", }) - : "Generate", - ), - ), + : "Generate" + ) + ) ), div( { class: () => "flex flex-col" }, @@ -222,7 +223,7 @@ export function AvatarPreview() { { class: () => "w-full flex flex-col gap-2 justify-center mt-24" }, div( { class: () => "flex text-black font-bold" }, - div({}, "We are launching OpenAI Assistant API integration soon"), + div({}, "We are launching OpenAI Assistant API integration soon") ), div( { class: () => "flex justify-center" }, @@ -240,6 +241,7 @@ export function AvatarPreview() { class: () => "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", onclick: async () => { + shareLoading.val = true; const url = await (await fetch("./get_webhook")).json(); await uploadPreview(); await fetch(url, { @@ -254,13 +256,19 @@ export function AvatarPreview() { content: "New register! \n" + email.val, }), }); + shareLoading.val = false; }, }, - "Get Avatar Link", - ), - ), - ), - ), - ), + () => + shareLoading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Get Avatar Link" + ) + ) + ) + ) + ) ); } diff --git a/js/dialog.js b/js/dialog.js index a609b66..b0463ec 100644 --- a/js/dialog.js +++ b/js/dialog.js @@ -8,6 +8,7 @@ export class InfoDialog extends ComfyDialog { createButtons() { return [ $el("button", { + class: 'z-[999]', type: "button", textContent: "Close", onclick: () => this.close(), @@ -26,6 +27,7 @@ export class InfoDialog extends ComfyDialog { this.textElement.replaceChildren(html); } this.element.style.display = "flex"; + this.element.style.zIndex = 1001; } } From 0093f036ed81f2fd0e1e6c0aa8cd88dec98b95fb Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Sat, 11 Nov 2023 00:36:29 +0800 Subject: [PATCH 015/127] fix: display image --- js/AvatarPreview.js | 28 +++++++++++++++++++++++----- js/dialog.js | 1 - js/state.js | 1 + 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 13f7da9..6864608 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -1,5 +1,11 @@ import { van } from "./van.js"; -import { imageUrl, showPreview, previewUrl, showEditor } from "./state.js"; +import { + imageUrl, + showPreview, + previewUrl, + showEditor, + previewImg, +} from "./state.js"; const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; import { uploadPreview } from "./index.js"; @@ -11,7 +17,7 @@ async function loadJSONWorkflow() { console.log(json); } -function uploadImage() { +async function uploadImage() { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; const nodes = graph.findNodesByType("LoadImage"); @@ -19,8 +25,14 @@ function uploadImage() { /** @type {any[]}*/ const widgets = nodes[0].widgets; console.log(nodes[0]); - console.log(nodes[0].widgets); widgets.find((x) => x.type == "button").callback(); + while (true) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + if (nodes[0]?.imgs) { + if (previewImg.val != '' && previewImg.val == nodes[0].imgs[0].currentSrc) continue; + return nodes[0].imgs[0].currentSrc; + } + } } const jsonWorkflowLoading = van.state(true); @@ -130,8 +142,8 @@ export function AvatarPreview() { { class: () => "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", - onclick: () => { - uploadImage(); + onclick: async () => { + previewImg.val = await uploadImage(); }, }, div({ class: "badge badge-neutral" }, "1"), @@ -142,6 +154,12 @@ export function AvatarPreview() { "data-inline": "false", }) ), + () => + previewImg.val != "" && + img({ + class: () => "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }), // label( // { // class: () => diff --git a/js/dialog.js b/js/dialog.js index b0463ec..4023da4 100644 --- a/js/dialog.js +++ b/js/dialog.js @@ -8,7 +8,6 @@ export class InfoDialog extends ComfyDialog { createButtons() { return [ $el("button", { - class: 'z-[999]', type: "button", textContent: "Close", onclick: () => this.close(), diff --git a/js/state.js b/js/state.js index 8d68713..b063e11 100644 --- a/js/state.js +++ b/js/state.js @@ -19,6 +19,7 @@ export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=400&height=400&hideTrigger=true"); +export const previewImg = van.state(""); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); export const fileName = van.state(''); From a2ec50597153c896f83fb598ce8d569fd37889f7 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Sat, 11 Nov 2023 00:41:03 +0800 Subject: [PATCH 016/127] chore: style --- js/AvatarPreview.js | 58 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 6864608..a523caf 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -29,7 +29,8 @@ async function uploadImage() { while (true) { await new Promise((resolve) => setTimeout(resolve, 1000)); if (nodes[0]?.imgs) { - if (previewImg.val != '' && previewImg.val == nodes[0].imgs[0].currentSrc) continue; + if (previewImg.val != "" && previewImg.val == nodes[0].imgs[0].currentSrc) + continue; return nodes[0].imgs[0].currentSrc; } } @@ -138,28 +139,7 @@ export function AvatarPreview() { // type: "file", // class: "file-input file-input-bordered w-full w-full", // }), - button( - { - class: () => - "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", - onclick: async () => { - previewImg.val = await uploadImage(); - }, - }, - div({ class: "badge badge-neutral" }, "1"), - div("Upload your image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", - }) - ), - () => - previewImg.val != "" && - img({ - class: () => "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }), + // label( // { // class: () => @@ -180,11 +160,35 @@ export function AvatarPreview() { // ), div( { - class: () => " w-full flex flex-col justify-center items-center", + class: () => + " w-full flex flex-col justify-center items-center gap-4", }, button( { - class: () => "btn mt-24 w-96 normal-case", + class: () => + "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + onclick: async () => { + previewImg.val = await uploadImage(); + }, + }, + div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }) + ), + () => + previewImg.val != "" + ? img({ + class: () => "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }) + : "", + button( + { + class: () => "btn w-96 normal-case", onclick: () => { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; @@ -205,7 +209,7 @@ export function AvatarPreview() { ), button( { - class: () => "btn mt-4 w-96 normal-case", + class: () => "btn w-96 normal-case", onclick: () => { const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); @@ -248,7 +252,7 @@ export function AvatarPreview() { input({ type: "text", class: () => - "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 text-xl", + "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 ", onchange: (e) => { email.val = e.target.value; }, From 5f03f2def96487066f68ead365d8b1b35ad66e08 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 10 Nov 2023 13:41:01 -0500 Subject: [PATCH 017/127] fix: image loading --- js/AvatarPreview.js | 11 +++++- js/state.js | 1 + js/tw-styles.css | 83 ++++++++++++--------------------------------- 3 files changed, 33 insertions(+), 62 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index a523caf..7aa9976 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -5,6 +5,7 @@ import { previewUrl, showEditor, previewImg, + previewImgLoading, } from "./state.js"; const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; @@ -21,6 +22,8 @@ async function uploadImage() { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; const nodes = graph.findNodesByType("LoadImage"); + previewImgLoading.val = true; + console.log(previewImgLoading.val); /** @type {any[]}*/ const widgets = nodes[0].widgets; @@ -31,6 +34,7 @@ async function uploadImage() { if (nodes[0]?.imgs) { if (previewImg.val != "" && previewImg.val == nodes[0].imgs[0].currentSrc) continue; + previewImgLoading.val = false; return nodes[0].imgs[0].currentSrc; } } @@ -177,7 +181,12 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "material-symbols:drive-folder-upload", "data-inline": "false", - }) + }), + () => (previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : '') ), () => previewImg.val != "" diff --git a/js/state.js b/js/state.js index b063e11..e3a230d 100644 --- a/js/state.js +++ b/js/state.js @@ -20,6 +20,7 @@ export const showEditor = van.state(false); export const showPreview = van.state(true); export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=400&height=400&hideTrigger=true"); export const previewImg = van.state(""); +export const previewImgLoading = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); export const fileName = van.state(''); diff --git a/js/tw-styles.css b/js/tw-styles.css index 25ee0be..c14b1ad 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:focus{ +.dropdown > *:not(summary):focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -840,16 +840,6 @@ html{ opacity: 1; } - .btm-nav > *.disabled:hover, - .btm-nav > *[disabled]:hover{ - pointer-events: none; - --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - --tw-text-opacity: 0.2; - } - .btn:hover{ --tw-border-opacity: 1; border-color: hsl(var(--b3) / var(--tw-border-opacity)); @@ -1003,8 +993,7 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 2.875rem; - min-height: 2.875rem; + height: 100%; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -1082,8 +1071,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; + border-start-end-radius: inherit; + border-end-end-radius: inherit; } .link{ @@ -1255,16 +1244,6 @@ html{ background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } -.btm-nav > *.disabled, - .btm-nav > *[disabled]{ - pointer-events: none; - --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - --tw-text-opacity: 0.2; -} - .btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; @@ -1669,6 +1648,11 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } +.input input:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1677,7 +1661,8 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus{ +.input:focus, + .input:focus-within{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1733,22 +1718,6 @@ details.collapse summary::-webkit-details-marker{ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.\!loading{ - pointer-events: none !important; - display: inline-block !important; - aspect-ratio: 1 / 1 !important; - width: 1.5rem !important; - background-color: currentColor !important; - -webkit-mask-size: 100% !important; - mask-size: 100% !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; - -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E") !important; - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E") !important; -} - .loading-spinner{ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); @@ -1766,9 +1735,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; + bottom: 0.75rem; left: 0px; top: 0.75rem; - bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1865,8 +1834,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - top: 50%; left: 0.5rem; + top: 50%; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1880,8 +1849,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - top: 50%; left: 1.25rem; + top: 50%; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -2111,8 +2080,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2134,8 +2103,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2150,8 +2119,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-top: -1px; margin-left: -0px; + margin-top: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2361,8 +2330,8 @@ details.collapse summary::-webkit-details-marker{ height: 394px; } -.h-\[400px\]{ - height: 400px; +.h-\[450px\]{ + height: 450px; } .h-full{ @@ -2373,10 +2342,6 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } -.h-\[450px\]{ - height: 450px; -} - .w-24{ width: 6rem; } @@ -2397,8 +2362,8 @@ details.collapse summary::-webkit-details-marker{ width: 24rem; } -.w-\[401px\]{ - width: 401px; +.w-\[450px\]{ + width: 450px; } .w-\[550px\]{ @@ -2414,10 +2379,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-\[450px\]{ - width: 450px; -} - .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); From abd1bc59d3b2bec3ec497afb481fde55a877c263 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 13 Nov 2023 11:20:56 +0800 Subject: [PATCH 018/127] chore: turn off debug mode --- js/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/state.js b/js/state.js index e3a230d..b0b6834 100644 --- a/js/state.js +++ b/js/state.js @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=true&width=400&height=400&hideTrigger=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); From 5ab3c62a2fe56dbbe7dab1adb25747045a5f9a46 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 13 Nov 2023 13:45:57 +0800 Subject: [PATCH 019/127] feat: mobile view --- js/AvatarPreview.js | 476 ++++++++++++++++++++++++-------------------- js/tw-styles.css | 116 +++++++++-- 2 files changed, 358 insertions(+), 234 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 7aa9976..6193a29 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -62,6 +62,242 @@ export function AvatarPreview() { const email = van.state(""); + const renderSteps = () => { + return div( + { + class: () => "flex flex-col bg-white justify-center", + }, + div( + { + class: () => + " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", + }, + "Avatech v1" + ), + div( + { + class: () => + " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", + }, + "Get your DALLE3 AI Personal Clone" + ), + // input({ + // type: "file", + // class: "file-input file-input-bordered w-full w-full", + // }), + + // label( + // { + // class: () => + // " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", + // // for: "imgtosam", + // onclick: () => {}, + // }, + // div( + // { + // class: () => " absolute flex justify-center items-center ", + // }, + // "upload your image" + // ), + // img({ + // class: () => " z-[10] object-contain w-[550px] h-[394px]", + // id: "imgUrl", + // }) + // ), + div( + { + class: () => + " w-full flex flex-col justify-center items-center gap-4", + }, + button( + { + class: () => + "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + onclick: async () => { + previewImg.val = await uploadImage(); + }, + }, + div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }), + () => + previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "" + ), + () => + previewImg.val != "" + ? img({ + class: () => "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }) + : "", + button( + { + class: () => "btn w-96 normal-case", + onclick: () => { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + + const nodes = graph.findNodesByType("SAM MultiLayer"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.type == "button").callback(); + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Edit segment" + ), + button( + { + class: () => "btn w-96 normal-case", + onclick: () => { + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + document.getElementById("queue-button").click(); + }, + }, + div({ class: "badge badge-neutral" }, "3"), + () => + loading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Generate" + ) + ) + ); + }; + + const renderIFrame = () => { + return iframe({ + id: "avatech-viewer-iframe", + title: "avatech-viewer-iframe", + name: "avatech-viewer-iframe", + allow: "cross-origin-isolated", + class: () => + "w-[450px] h-[450px] z-[100] pointer-events-auto flex border-none overflow-hidden" + + (showPreview.val ? "" : "hidden"), + // src: "https://labs.avatech.ai/viewer/default", + // src: "http://localhost:3000/viewer/default", + src: previewUrl, + }); + }; + + const renderShareLink = () => { + return div( + { class: () => "w-full flex flex-col gap-2 justify-center mt-8" }, + div( + { class: () => "w-full flex justify-center text-black font-bold" }, + span("We are launching OpenAI Assistant API integration soon") + ), + div( + { class: () => "flex justify-center" }, + input({ + type: "text", + class: () => + "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 ", + onchange: (e) => { + email.val = e.target.value; + }, + placeholder: "Enter your email", + }), + button( + { + class: () => + "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", + onclick: async () => { + shareLoading.val = true; + const url = await (await fetch("./get_webhook")).json(); + await uploadPreview(); + await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username: "Avabot", + avatar_url: + "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", + content: "New register! \n" + email.val, + }), + }); + shareLoading.val = false; + }, + }, + () => + shareLoading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Get Avatar Link" + ) + ) + ); + }; + + const renderCloseButton = () => { + return button( + { + class: () => + "btn flex flex-row btn-ghost text-black normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + onclick: () => { + showPreview.val = false; + }, + }, + span({ + class: "iconify text-lg", + "data-icon": "ic:round-close", + "data-inline": "false", + }) + ); + }; + + const renderChangeWorkflowButton = () => { + return button( + { + class: () => + "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + onclick: () => { + document.getElementById("comfy-load-button").click(); + }, + }, + span({ + class: "iconify text-lg", + "data-icon": "ic:round-swap-vert", + "data-inline": "false", + }), + () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") + ); + }; + + const renderTwitter = () => { + return button( + { + class: () => + "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", + onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), + }, + "Twitter" + ); + }; + + const isMobileDevice = () => { + return window.screen.width < 640; + }; + return div( { class: () => { @@ -69,7 +305,7 @@ export function AvatarPreview() { return ( (showPreview.val ? "" : "hidden ") + - "w-full h-screen absolute left-0 top-0 z-[99] pointer-events-auto flex border-none " + "w-full h-screen absolute left-0 top-0 z-[99] pointer-events-auto flex border-none" ); }, }, @@ -77,229 +313,37 @@ export function AvatarPreview() { { class: "absolute top-4 left-4 flex flex-row gap-2", }, - button( - { - class: () => - "btn flex flex-row btn-ghost text-black normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", - onclick: () => { - showPreview.val = false; - }, - }, - span({ - class: "iconify text-lg", - "data-icon": "ic:round-close", - "data-inline": "false", - }) - ), - button( - { - class: () => - "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", - onclick: () => { - document.getElementById("comfy-load-button").click(); - }, - }, - span({ - class: "iconify text-lg", - "data-icon": "ic:round-swap-vert", - "data-inline": "false", - }), - () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") - ) + renderCloseButton(), + renderChangeWorkflowButton() ), - button( - { - class: () => - "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", - onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), - }, - "Twitter" - ), - div( - { - class: () => - "flex w-full h-full bg-white justify-around items-center p-24" + - (showPreview.val ? "" : "hidden"), - }, - div( - { - class: () => "flex flex-col bg-white justify-center h-full", - }, - div( + renderTwitter(), + () => { + if (isMobileDevice()) { + return div( { class: () => - " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", + "flex flex-col w-full h-[110%] bg-white justify-center items-center py-16 px-4 gap-2" + + (showPreview.val ? "" : "hidden"), }, - "Avatech v1" - ), - div( + renderIFrame(), + renderSteps(), + renderShareLink() + ); + } else { + return div( { class: () => - " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", + "flex w-full h-full bg-white justify-around items-center p-24" + + (showPreview.val ? "" : "hidden"), }, - "Get your DALLE3 AI Personal Clone" - ), - // input({ - // type: "file", - // class: "file-input file-input-bordered w-full w-full", - // }), - - // label( - // { - // class: () => - // " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", - // // for: "imgtosam", - // onclick: () => {}, - // }, - // div( - // { - // class: () => " absolute flex justify-center items-center ", - // }, - // "upload your image" - // ), - // img({ - // class: () => " z-[10] object-contain w-[550px] h-[394px]", - // id: "imgUrl", - // }) - // ), - div( - { - class: () => - " w-full flex flex-col justify-center items-center gap-4", - }, - button( - { - class: () => - "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", - onclick: async () => { - previewImg.val = await uploadImage(); - }, - }, - div({ class: "badge badge-neutral" }, "1"), - div("Upload your image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", - }), - () => (previewImgLoading.val - ? span({ - class: "loading loading-spinner loading-lg", - }) - : '') - ), - () => - previewImg.val != "" - ? img({ - class: () => "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }) - : "", - button( - { - class: () => "btn w-96 normal-case", - onclick: () => { - /** @type {import('../../../web/types/litegraph.js').LGraph}*/ - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - - const nodes = graph.findNodesByType("SAM MultiLayer"); - - /** @type {any[]}*/ - const widgets = nodes[0].widgets; - console.log(nodes[0]); - console.log(nodes[0].widgets); - widgets.find((x) => x.type == "button").callback(); - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit segment" - ), - button( - { - class: () => "btn w-96 normal-case", - onclick: () => { - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - document.getElementById("queue-button").click(); - }, - }, - div({ class: "badge badge-neutral" }, "3"), - () => - loading.val - ? span({ - class: "loading loading-spinner loading-lg", - }) - : "Generate" - ) - ) - ), - div( - { class: () => "flex flex-col" }, - iframe({ - id: "avatech-viewer-iframe", - title: "avatech-viewer-iframe", - name: "avatech-viewer-iframe", - allow: "cross-origin-isolated", - class: () => - "w-[450px] h-[450px] z-[100] pointer-events-auto flex border-none overflow-hidden" + - (showPreview.val ? "" : "hidden"), - // src: "https://labs.avatech.ai/viewer/default", - // src: "http://localhost:3000/viewer/default", - src: previewUrl, - }), - div( - { class: () => "w-full flex flex-col gap-2 justify-center mt-24" }, + renderSteps(), div( - { class: () => "flex text-black font-bold" }, - div({}, "We are launching OpenAI Assistant API integration soon") - ), - div( - { class: () => "flex justify-center" }, - input({ - type: "text", - class: () => - "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 ", - onchange: (e) => { - email.val = e.target.value; - }, - placeholder: "Enter your email", - }), - button( - { - class: () => - "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", - onclick: async () => { - shareLoading.val = true; - const url = await (await fetch("./get_webhook")).json(); - await uploadPreview(); - await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: "Avabot", - avatar_url: - "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", - content: "New register! \n" + email.val, - }), - }); - shareLoading.val = false; - }, - }, - () => - shareLoading.val - ? span({ - class: "loading loading-spinner loading-lg", - }) - : "Get Avatar Link" - ) + { class: () => "flex flex-col" }, + renderIFrame(), + renderShareLink() ) - ) - ) - ) + ); + } + } ); } diff --git a/js/tw-styles.css b/js/tw-styles.css index c14b1ad..45bc010 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:not(summary):focus{ +.dropdown > *:focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -993,7 +993,8 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 100%; + height: 2.875rem; + min-height: 2.875rem; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -1071,8 +1072,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-start-end-radius: inherit; - border-end-end-radius: inherit; + border-top-right-radius: inherit; + border-bottom-right-radius: inherit; } .link{ @@ -1648,11 +1649,6 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } -.input input:focus{ - outline: 2px solid transparent; - outline-offset: 2px; -} - .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1661,8 +1657,7 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus, - .input:focus-within{ +.input:focus{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1735,9 +1730,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; - bottom: 0.75rem; left: 0px; top: 0.75rem; + bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1834,8 +1829,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - left: 0.5rem; top: 50%; + left: 0.5rem; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1849,8 +1844,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - left: 1.25rem; top: 50%; + left: 1.25rem; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -2080,8 +2075,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2103,8 +2098,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -2119,8 +2114,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-left: -0px; margin-top: -1px; + margin-left: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2282,6 +2277,11 @@ details.collapse summary::-webkit-details-marker{ margin-bottom: 0px; } +.mx-4{ + margin-left: 1rem; + margin-right: 1rem; +} + .mb-2{ margin-bottom: 0.5rem; } @@ -2302,6 +2302,22 @@ details.collapse summary::-webkit-details-marker{ margin-top: 1rem; } +.mt-8{ + margin-top: 2rem; +} + +.mt-\[48rem\]{ + margin-top: 48rem; +} + +.mt-\[12rem\]{ + margin-top: 12rem; +} + +.mt-\[16rem\]{ + margin-top: 16rem; +} + .block{ display: block; } @@ -2342,6 +2358,22 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } +.h-\[24rem\]{ + height: 24rem; +} + +.\!h-\[450px\]{ + height: 450px !important; +} + +.h-\[110\%\]{ + height: 110%; +} + +.max-h-\[450px\]{ + max-height: 450px; +} + .w-24{ width: 6rem; } @@ -2379,6 +2411,14 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-auto{ + width: auto; +} + +.max-w-\[450px\]{ + max-width: 450px; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2441,6 +2481,22 @@ details.collapse summary::-webkit-details-marker{ overflow: hidden; } +.overflow-visible{ + overflow: visible; +} + +.\!overflow-visible{ + overflow: visible !important; +} + +.\!overflow-x-hidden{ + overflow-x: hidden !important; +} + +.\!overflow-y-scroll{ + overflow-y: scroll !important; +} + .rounded{ border-radius: 0.25rem; } @@ -2654,6 +2710,30 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } +.py-12{ + padding-top: 3rem; + padding-bottom: 3rem; +} + +.py-16{ + padding-top: 4rem; + padding-bottom: 4rem; +} + +.py-24{ + padding-top: 6rem; + padding-bottom: 6rem; +} + +.px-12{ + padding-left: 3rem; + padding-right: 3rem; +} + +.text-center{ + text-align: center; +} + .text-start{ text-align: start; } From 2b76964b59f6a846a53ecebb9646685b6ce8d7dd Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 13 Nov 2023 15:15:11 +0800 Subject: [PATCH 020/127] fix: mask canvas size --- js/LayerEditor.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index e5b561c..d81f623 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -240,13 +240,16 @@ export function LayerEditor() { } }, }), - canvas({ - class: - "pointer-events-none fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-80", - id: "mask-canvas", - }), - () => { - return div( + () => + canvas({ + class: + "pointer-events-none fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-80", + style: () => + `width: ${imageContainerSize.val.width}px; height: ${imageContainerSize.val.height}px;`, + id: "mask-canvas", + }), + () => + div( { class: "absolute w-full h-full pointer-events-none", style: () => @@ -274,8 +277,7 @@ export function LayerEditor() { }, }); }) - ); - } + ) ), SideBar() ); From 0ad020beab55c348b43aea93e19535dbef0c8f00 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 13 Nov 2023 15:47:15 +0800 Subject: [PATCH 021/127] feat: button stage --- js/AvatarPreview.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 6193a29..983fbb5 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -61,6 +61,7 @@ export function AvatarPreview() { }); const email = van.state(""); + const stage = van.state(0); // 0: upload image, 1: edit segment, 2: generate const renderSteps = () => { return div( @@ -115,6 +116,7 @@ export function AvatarPreview() { "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", onclick: async () => { previewImg.val = await uploadImage(); + stage.val = 1; }, }, div({ class: "badge badge-neutral" }, "1"), @@ -140,7 +142,8 @@ export function AvatarPreview() { : "", button( { - class: () => "btn w-96 normal-case", + class: () => + "btn w-96 normal-case " + (stage.val < 1 ? "btn-disabled" : ""), onclick: () => { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; @@ -154,6 +157,7 @@ export function AvatarPreview() { console.log(nodes[0]); console.log(nodes[0].widgets); widgets.find((x) => x.type == "button").callback(); + stage.val = 2; }, }, div({ class: "badge badge-neutral" }, "2"), @@ -161,7 +165,8 @@ export function AvatarPreview() { ), button( { - class: () => "btn w-96 normal-case", + class: () => + "btn w-96 normal-case " + (stage.val < 2 ? "btn-disabled" : ""), onclick: () => { const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); From 961e9dfef3c067ace0cd3fb3d9f8ef932b2342c9 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Mon, 13 Nov 2023 03:13:58 -0500 Subject: [PATCH 022/127] workflow link --- routes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/routes.py b/routes.py index 7f0049d..919b572 100644 --- a/routes.py +++ b/routes.py @@ -98,8 +98,9 @@ async def post_sam_model(request): @server.PromptServer.instance.routes.get("/get_default_workflow") async def get_default_workflow(request): - # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" - json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" + # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow + # json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" # Benny workflow + json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173524740577693767/1113_2.json?ex=65644515&is=6551d015&hm=b857628ee83d0006f88d1076851cc077c7a9e0bc0ba9e7647d3cce30b8ad7758&' # Deepwoo workflow response = requests.get(json_link) response.raise_for_status() return web.json_response(response.json()) From 92d8456b896b2a567cbe80d01a5bf80ae5ae93dc Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Mon, 13 Nov 2023 03:15:08 -0500 Subject: [PATCH 023/127] fix --- routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.py b/routes.py index 919b572..ee9a924 100644 --- a/routes.py +++ b/routes.py @@ -100,7 +100,7 @@ async def post_sam_model(request): async def get_default_workflow(request): # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow # json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" # Benny workflow - json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173524740577693767/1113_2.json?ex=65644515&is=6551d015&hm=b857628ee83d0006f88d1076851cc077c7a9e0bc0ba9e7647d3cce30b8ad7758&' # Deepwoo workflow + json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173536418337914970/newstyle.json?ex=65644ff5&is=6551daf5&hm=f129838fae10197351bd27c69c7ff5eb4edf2c7d6ed74e6db8b55ddaa3c77dee&' # Deepwoo workflow response = requests.get(json_link) response.raise_for_status() return web.json_response(response.json()) From 52b411268474810165ecb634f8197355064d5550 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 13 Nov 2023 16:25:42 +0800 Subject: [PATCH 024/127] feat: update components style --- js/AvatarPreview.js | 17 ++- js/tw-styles.css | 340 +++++--------------------------------------- 2 files changed, 46 insertions(+), 311 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 983fbb5..86805fe 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -129,7 +129,7 @@ export function AvatarPreview() { () => previewImgLoading.val ? span({ - class: "loading loading-spinner loading-lg", + class: "loading loading-spinner loading-md", }) : "" ), @@ -203,17 +203,20 @@ export function AvatarPreview() { const renderShareLink = () => { return div( - { class: () => "w-full flex flex-col gap-2 justify-center mt-8" }, + { + class: () => + "w-full flex flex-col gap-2 justify-center items-center mt-8", + }, div( - { class: () => "w-full flex justify-center text-black font-bold" }, - span("We are launching OpenAI Assistant API integration soon") + { class: () => "w-full flex justify-center text-black font-bold italic text-gray-500" }, + span("We are launching OpenAI Assistant API integration soon!") ), div( - { class: () => "flex justify-center" }, + { class: () => "w-[24rem] flex justify-center items-center" }, input({ type: "text", class: () => - "border !border-r-0 border-black rounded-l-lg ring-black/30 p-1 text-black h-12 ", + "w-full input input-bordered text-black rounded rounded-l-md rounded-r-none !outline-none", onchange: (e) => { email.val = e.target.value; }, @@ -222,7 +225,7 @@ export function AvatarPreview() { button( { class: () => - "btn btn-outline !rounded-l-none !rounded-r-lg p-1 normal-case", + "btn rounded rounded-l-none rounded-r-md no-animation bg-neutral hover:bg-neutral-focus text-white border-none normal-case", onclick: async () => { shareLoading.val = true; const url = await (await fetch("./get_webhook")).json(); diff --git a/js/tw-styles.css b/js/tw-styles.css index 45bc010..aeb2978 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -865,78 +865,6 @@ html{ --tw-bg-opacity: 0.2; } - .btn-outline:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--bc) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--b1) / var(--tw-text-opacity)); - } - - .btn-outline.btn-primary:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--pf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--pf) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--pc) / var(--tw-text-opacity)); - } - - .btn-outline.btn-secondary:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--sf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--sf) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--sc) / var(--tw-text-opacity)); - } - - .btn-outline.btn-accent:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--af) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--af) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--ac) / var(--tw-text-opacity)); - } - - .btn-outline.btn-success:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--su) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--su) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--suc) / var(--tw-text-opacity)); - } - - .btn-outline.btn-info:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--in) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--in) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--inc) / var(--tw-text-opacity)); - } - - .btn-outline.btn-warning:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--wa) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--wa) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--wac) / var(--tw-text-opacity)); - } - - .btn-outline.btn-error:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--er) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--er) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--erc) / var(--tw-text-opacity)); - } - .btn-disabled:hover, .btn[disabled]:hover, .btn:disabled:hover{ @@ -1308,123 +1236,6 @@ html{ --tw-bg-opacity: 0.2; } -.btn-outline{ - border-color: currentColor; - background-color: transparent; - --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.btn-outline.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--bc) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--b1) / var(--tw-text-opacity)); -} - -.btn-outline.btn-primary{ - --tw-text-opacity: 1; - color: hsl(var(--p) / var(--tw-text-opacity)); -} - -.btn-outline.btn-primary.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--pf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--pf) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--pc) / var(--tw-text-opacity)); -} - -.btn-outline.btn-secondary{ - --tw-text-opacity: 1; - color: hsl(var(--s) / var(--tw-text-opacity)); -} - -.btn-outline.btn-secondary.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--sf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--sf) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--sc) / var(--tw-text-opacity)); -} - -.btn-outline.btn-accent{ - --tw-text-opacity: 1; - color: hsl(var(--a) / var(--tw-text-opacity)); -} - -.btn-outline.btn-accent.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--af) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--af) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--ac) / var(--tw-text-opacity)); -} - -.btn-outline.btn-success{ - --tw-text-opacity: 1; - color: hsl(var(--su) / var(--tw-text-opacity)); -} - -.btn-outline.btn-success.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--su) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--su) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--suc) / var(--tw-text-opacity)); -} - -.btn-outline.btn-info{ - --tw-text-opacity: 1; - color: hsl(var(--in) / var(--tw-text-opacity)); -} - -.btn-outline.btn-info.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--in) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--in) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--inc) / var(--tw-text-opacity)); -} - -.btn-outline.btn-warning{ - --tw-text-opacity: 1; - color: hsl(var(--wa) / var(--tw-text-opacity)); -} - -.btn-outline.btn-warning.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--wa) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--wa) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--wac) / var(--tw-text-opacity)); -} - -.btn-outline.btn-error{ - --tw-text-opacity: 1; - color: hsl(var(--er) / var(--tw-text-opacity)); -} - -.btn-outline.btn-error.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--er) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--er) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--erc) / var(--tw-text-opacity)); -} - .btn.btn-disabled, .btn[disabled], .btn:disabled{ @@ -1718,6 +1529,10 @@ details.collapse summary::-webkit-details-marker{ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } +.loading-md{ + width: 1.5rem; +} + .loading-lg{ width: 2.5rem; } @@ -2277,11 +2092,6 @@ details.collapse summary::-webkit-details-marker{ margin-bottom: 0px; } -.mx-4{ - margin-left: 1rem; - margin-right: 1rem; -} - .mb-2{ margin-bottom: 0.5rem; } @@ -2306,18 +2116,6 @@ details.collapse summary::-webkit-details-marker{ margin-top: 2rem; } -.mt-\[48rem\]{ - margin-top: 48rem; -} - -.mt-\[12rem\]{ - margin-top: 12rem; -} - -.mt-\[16rem\]{ - margin-top: 16rem; -} - .block{ display: block; } @@ -2334,14 +2132,14 @@ details.collapse summary::-webkit-details-marker{ aspect-ratio: 1 / 1; } -.h-12{ - height: 3rem; -} - .h-3{ height: 0.75rem; } +.h-\[110\%\]{ + height: 110%; +} + .h-\[394px\]{ height: 394px; } @@ -2358,22 +2156,6 @@ details.collapse summary::-webkit-details-marker{ height: 100vh; } -.h-\[24rem\]{ - height: 24rem; -} - -.\!h-\[450px\]{ - height: 450px !important; -} - -.h-\[110\%\]{ - height: 110%; -} - -.max-h-\[450px\]{ - max-height: 450px; -} - .w-24{ width: 6rem; } @@ -2411,14 +2193,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-auto{ - width: auto; -} - -.max-w-\[450px\]{ - max-width: 450px; -} - .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2481,22 +2255,6 @@ details.collapse summary::-webkit-details-marker{ overflow: hidden; } -.overflow-visible{ - overflow: visible; -} - -.\!overflow-visible{ - overflow: visible !important; -} - -.\!overflow-x-hidden{ - overflow-x: hidden !important; -} - -.\!overflow-y-scroll{ - overflow-y: scroll !important; -} - .rounded{ border-radius: 0.25rem; } @@ -2517,21 +2275,6 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.125rem; } -.\!rounded-l-none{ - border-top-left-radius: 0px !important; - border-bottom-left-radius: 0px !important; -} - -.\!rounded-r-lg{ - border-top-right-radius: 0.5rem !important; - border-bottom-right-radius: 0.5rem !important; -} - -.rounded-l-lg{ - border-top-left-radius: 0.5rem; - border-bottom-left-radius: 0.5rem; -} - .rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -2556,10 +2299,6 @@ details.collapse summary::-webkit-details-marker{ border-width: 1px; } -.\!border-r-0{ - border-right-width: 0px !important; -} - .border-t-4{ border-top-width: 4px; } @@ -2568,11 +2307,6 @@ details.collapse summary::-webkit-details-marker{ border-style: none; } -.border-black{ - --tw-border-opacity: 1; - border-color: rgb(0 0 0 / var(--tw-border-opacity)); -} - .border-orange-500{ --tw-border-opacity: 1; border-color: rgb(249 115 22 / var(--tw-border-opacity)); @@ -2674,10 +2408,6 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } -.p-1{ - padding: 0.25rem; -} - .p-2{ padding: 0.5rem; } @@ -2705,33 +2435,14 @@ details.collapse summary::-webkit-details-marker{ padding-right: 1rem; } -.py-2{ - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.py-12{ - padding-top: 3rem; - padding-bottom: 3rem; -} - .py-16{ padding-top: 4rem; padding-bottom: 4rem; } -.py-24{ - padding-top: 6rem; - padding-bottom: 6rem; -} - -.px-12{ - padding-left: 3rem; - padding-right: 3rem; -} - -.text-center{ - text-align: center; +.py-2{ + padding-top: 0.5rem; + padding-bottom: 0.5rem; } .text-start{ @@ -2775,6 +2486,10 @@ details.collapse summary::-webkit-details-marker{ text-transform: none; } +.italic{ + font-style: italic; +} + .text-base-content{ --tw-text-opacity: 1; color: hsl(var(--bc) / var(--tw-text-opacity)); @@ -2804,10 +2519,25 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } +.text-gray-500{ + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + .opacity-80{ opacity: 0.8; } +.\!outline-none{ + outline: 2px solid transparent !important; + outline-offset: 2px !important; +} + +.outline-none{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .ring-1{ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); @@ -2819,10 +2549,6 @@ details.collapse summary::-webkit-details-marker{ --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); } -.ring-black\/30{ - --tw-ring-color: rgb(0 0 0 / 0.3); -} - .filter{ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -3002,3 +2728,9 @@ img[src] { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); } + +.focus\:ring-0:focus{ + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} From fe4de7eb28d6bd90c8004378f1e92ada438524ed Mon Sep 17 00:00:00 2001 From: BennyKok Date: Mon, 13 Nov 2023 00:39:23 -0800 Subject: [PATCH 025/127] add audio selector --- js/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/state.js b/js/state.js index b0b6834..65d0ea9 100644 --- a/js/state.js +++ b/js/state.js @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); From e49e199641696fe7094ceaed374287c06bdfb59a Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Mon, 13 Nov 2023 04:13:17 -0500 Subject: [PATCH 026/127] fix: viewer layout --- js/AvatarPreview.js | 76 +++++++++++++++++++++------------------- js/state.js | 2 +- js/tw-styles.css | 85 +++++++++++++++++++++++---------------------- 3 files changed, 86 insertions(+), 77 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 86805fe..827a97e 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -193,7 +193,7 @@ export function AvatarPreview() { name: "avatech-viewer-iframe", allow: "cross-origin-isolated", class: () => - "w-[450px] h-[450px] z-[100] pointer-events-auto flex border-none overflow-hidden" + + "w-full h-full min-w-[300px] min-h-[600px] z-[100] pointer-events-auto flex border-none overflow-hidden" + (showPreview.val ? "" : "hidden"), // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", @@ -208,7 +208,10 @@ export function AvatarPreview() { "w-full flex flex-col gap-2 justify-center items-center mt-8", }, div( - { class: () => "w-full flex justify-center text-black font-bold italic text-gray-500" }, + { + class: () => + "w-full flex justify-center font-bold italic text-gray-500", + }, span("We are launching OpenAI Assistant API integration soon!") ), div( @@ -313,45 +316,48 @@ export function AvatarPreview() { return ( (showPreview.val ? "" : "hidden ") + - "w-full h-screen absolute left-0 top-0 z-[99] pointer-events-auto flex border-none" + "w-full h-full absolute left-0 top-0 z-[99] pointer-events-auto flex border-none" ); }, }, div( - { - class: "absolute top-4 left-4 flex flex-row gap-2", - }, - renderCloseButton(), - renderChangeWorkflowButton() - ), - renderTwitter(), - () => { - if (isMobileDevice()) { - return div( - { - class: () => - "flex flex-col w-full h-[110%] bg-white justify-center items-center py-16 px-4 gap-2" + - (showPreview.val ? "" : "hidden"), - }, - renderIFrame(), - renderSteps(), - renderShareLink() - ); - } else { - return div( - { - class: () => - "flex w-full h-full bg-white justify-around items-center p-24" + - (showPreview.val ? "" : "hidden"), - }, - renderSteps(), - div( - { class: () => "flex flex-col" }, + { class: "overflow-y-auto overflow-x-hidden w-full h-full" }, + div( + { + class: "absolute top-4 left-4 flex flex-row gap-2", + }, + renderCloseButton(), + renderChangeWorkflowButton() + ), + renderTwitter(), + () => { + if (isMobileDevice()) { + return div( + { + class: () => + "flex flex-col w-full h-fit bg-white justify-center items-center py-16 px-4 gap-2" + + (showPreview.val ? "" : "hidden"), + }, renderIFrame(), + renderSteps(), renderShareLink() - ) - ); + ); + } else { + return div( + { + class: () => + "flex w-full h-full bg-white justify-around items-center p-24" + + (showPreview.val ? "" : "hidden"), + }, + renderSteps(), + div( + { class: () => "flex flex-col" }, + renderIFrame(), + renderShareLink() + ) + ); + } } - } + ) ); } diff --git a/js/state.js b/js/state.js index 65d0ea9..104e187 100644 --- a/js/state.js +++ b/js/state.js @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=300&height=300&hideTrigger=true&voiceSelection=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); diff --git a/js/tw-styles.css b/js/tw-styles.css index aeb2978..422d630 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:focus{ +.dropdown > *:not(summary):focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -921,8 +921,7 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 2.875rem; - min-height: 2.875rem; + height: 100%; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -1000,8 +999,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; + border-start-end-radius: inherit; + border-end-end-radius: inherit; } .link{ @@ -1460,6 +1459,11 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } +.input input:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1468,7 +1472,8 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus{ +.input:focus, + .input:focus-within{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1545,9 +1550,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; + bottom: 0.75rem; left: 0px; top: 0.75rem; - bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1644,8 +1649,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - top: 50%; left: 0.5rem; + top: 50%; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1659,8 +1664,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - top: 50%; left: 1.25rem; + top: 50%; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -1890,8 +1895,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -1913,8 +1918,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-top: -0px; margin-left: -1px; + margin-top: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -1929,8 +1934,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-top: -1px; margin-left: -0px; + margin-top: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2136,24 +2141,21 @@ details.collapse summary::-webkit-details-marker{ height: 0.75rem; } -.h-\[110\%\]{ - height: 110%; -} - .h-\[394px\]{ height: 394px; } -.h-\[450px\]{ - height: 450px; +.h-fit{ + height: -moz-fit-content; + height: fit-content; } .h-full{ height: 100%; } -.h-screen{ - height: 100vh; +.min-h-\[600px\]{ + min-height: 600px; } .w-24{ @@ -2176,10 +2178,6 @@ details.collapse summary::-webkit-details-marker{ width: 24rem; } -.w-\[450px\]{ - width: 450px; -} - .w-\[550px\]{ width: 550px; } @@ -2193,6 +2191,14 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.min-w-\[250px\]{ + min-width: 250px; +} + +.min-w-\[300px\]{ + min-width: 300px; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2255,6 +2261,14 @@ details.collapse summary::-webkit-details-marker{ overflow: hidden; } +.overflow-y-auto{ + overflow-y: auto; +} + +.overflow-x-hidden{ + overflow-x: hidden; +} + .rounded{ border-radius: 0.25rem; } @@ -2505,6 +2519,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(209 213 219 / var(--tw-text-opacity)); } +.text-gray-500{ + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + .text-orange-700{ --tw-text-opacity: 1; color: rgb(194 65 12 / var(--tw-text-opacity)); @@ -2519,11 +2538,6 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-gray-500{ - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); -} - .opacity-80{ opacity: 0.8; } @@ -2533,11 +2547,6 @@ details.collapse summary::-webkit-details-marker{ outline-offset: 2px !important; } -.outline-none{ - outline: 2px solid transparent; - outline-offset: 2px; -} - .ring-1{ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); @@ -2728,9 +2737,3 @@ img[src] { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); } - -.focus\:ring-0:focus{ - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} From 631277dfbaead8f8498c254dbd70176b02ab78ca Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 02:19:22 +0800 Subject: [PATCH 027/127] fix: unify to avatarId --- js/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/index.js b/js/index.js index 9d5cca9..1f3542e 100644 --- a/js/index.js +++ b/js/index.js @@ -656,7 +656,7 @@ export async function uploadPreview() { }).catch((error) => console.error(error)); infoDialog.show( - `Preview avatar url: https://editor.avatech.ai/viewer?objectId=` + + `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + labData.modelId + ``, ); @@ -733,7 +733,7 @@ function injectUIComponentToComfyuimenu() { ).catch((error) => console.error(error)); infoDialog.show( - `Preview updated: https://editor.avatech.ai/viewer?objectId=` + + `Preview updated: https://editor.avatech.ai/viewer?avatarId=` + labData.modelId + "\n Remember to hard refresh before checking out the new preview!", ); @@ -773,7 +773,7 @@ function injectUIComponentToComfyuimenu() { shareAvatar.append(dropdown); } else { infoDialog.show( - `Preview avatar url: https://editor.avatech.ai/viewer?objectId=` + + `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + previewModelId.val + ``, ); From a91a92318df5dd0c3c3ce75ca05b723e1f568fef Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 02:34:42 +0800 Subject: [PATCH 028/127] fix: layer editor clear all --- js/SideBar.js | 16 ++++++++++++++-- js/tw-styles.css | 35 +++++++++++++---------------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/js/SideBar.js b/js/SideBar.js index dbd39c1..ec5836a 100644 --- a/js/SideBar.js +++ b/js/SideBar.js @@ -27,7 +27,8 @@ van.derive(() => { if ( showImageEditor.val && targetNode.val != undefined && - targetNode.val.outputs && targetNode.val.type === 'SAM MultiLayer' + targetNode.val.outputs && + targetNode.val.type === "SAM MultiLayer" ) { const outputNames = targetNode.val.outputs.map((x) => x.name).slice(1); const record = Object.keys(imagePromptsMulti.val); @@ -64,13 +65,24 @@ export function SideBar() { class: "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 left-0 bottom-0 items-start gap-2", }, - () => { const layers = Object.entries(imagePromptsMulti.val); return ul( { class: "menu bg-base-200 w-56 rounded-box text-base-content ", }, + button({ + onclick: () => { + const layers = Object.entries(imagePromptsMulti.val); + layers.map(([key, value]) => { + imagePrompts.val = []; + imagePromptsMulti.val[key] = []; + }); + drawSegment([]); + updateImagePrompts(); + }, + class: "btn btn-ghost normal-case flex", + }, "Clear ALL"), layers.length === 0 ? li(a("Empty layer")) : null, ...layers.map(([key, value]) => { return li( diff --git a/js/tw-styles.css b/js/tw-styles.css index 422d630..748e73b 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ /* @@ -803,7 +803,7 @@ html{ display: inline-block; } -.dropdown > *:not(summary):focus{ +.dropdown > *:focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -921,7 +921,8 @@ html{ .file-input::file-selector-button{ margin-right: 1rem; display: inline-flex; - height: 100%; + height: 2.875rem; + min-height: 2.875rem; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -999,8 +1000,8 @@ html{ .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-start-end-radius: inherit; - border-end-end-radius: inherit; + border-top-right-radius: inherit; + border-bottom-right-radius: inherit; } .link{ @@ -1459,11 +1460,6 @@ details.collapse summary::-webkit-details-marker{ --tw-text-opacity: 0.2; } -.input input:focus{ - outline: 2px solid transparent; - outline-offset: 2px; -} - .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } @@ -1472,8 +1468,7 @@ details.collapse summary::-webkit-details-marker{ --tw-border-opacity: 0.2; } -.input:focus, - .input:focus-within{ +.input:focus{ outline-style: solid; outline-width: 2px; outline-offset: 2px; @@ -1550,9 +1545,9 @@ details.collapse summary::-webkit-details-marker{ .menu :where(li ul):before{ position: absolute; - bottom: 0.75rem; left: 0px; top: 0.75rem; + bottom: 0.75rem; width: 1px; background-color: hsl(var(--bc) / 0.1); content: ""; @@ -1649,8 +1644,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - left: 0.5rem; top: 50%; + left: 0.5rem; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1664,8 +1659,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - left: 1.25rem; top: 50%; + left: 1.25rem; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -1895,8 +1890,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -1918,8 +1913,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-horizontal .btn:first-child:not(:last-child){ - margin-left: -1px; margin-top: -0px; + margin-left: -1px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: 0; border-bottom-left-radius: var(--rounded-btn, 0.5rem); @@ -1934,8 +1929,8 @@ details.collapse summary::-webkit-details-marker{ } .btn-group-vertical .btn:first-child:not(:last-child){ - margin-left: -0px; margin-top: -1px; + margin-left: -0px; border-top-left-radius: var(--rounded-btn, 0.5rem); border-top-right-radius: var(--rounded-btn, 0.5rem); border-bottom-left-radius: 0; @@ -2191,10 +2186,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.min-w-\[250px\]{ - min-width: 250px; -} - .min-w-\[300px\]{ min-width: 300px; } From 4f8b27206cabfc7b04660c7a73f072285b64de87 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 14 Nov 2023 12:00:59 +0800 Subject: [PATCH 029/127] fix: segmentation --- js/Alert.js | 2 +- js/AvatarPreview.js | 6 +++- js/LayerEditor.js | 73 ++++++++++++++++++++++++++++++++------------- routes.py | 31 +++++++++++-------- 4 files changed, 77 insertions(+), 35 deletions(-) diff --git a/js/Alert.js b/js/Alert.js index aa477d1..2dab430 100644 --- a/js/Alert.js +++ b/js/Alert.js @@ -14,7 +14,7 @@ export function Alert() { return div( { class: () => - "absolute bottom-8 flex justify-center w-full " + + "absolute z-[100] bottom-8 flex justify-center w-full " + (alertDialog.val.text ? "" : "hidden"), }, div( diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 827a97e..9381e1a 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -11,6 +11,7 @@ const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; import { uploadPreview } from "./index.js"; import { api } from "./api.js"; +import { uploadSegments } from "./LayerEditor.js"; async function loadJSONWorkflow() { const json = await (await fetch("./get_default_workflow")).json(); @@ -167,7 +168,10 @@ export function AvatarPreview() { { class: () => "btn w-96 normal-case " + (stage.val < 2 ? "btn-disabled" : ""), - onclick: () => { + onclick: async () => { + const uploaded = await uploadSegments(); + if (!uploaded) return; + const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); if (!imageNodes[0].imgs) return; diff --git a/js/LayerEditor.js b/js/LayerEditor.js index d81f623..675ec12 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -13,6 +13,7 @@ import { imagePromptsMulti, embeddings, embeddingID, + alertDialog, } from "./state.js"; import { van } from "./van.js"; const { button, div, img, canvas, span } = van.tags; @@ -29,17 +30,17 @@ export function updateImagePrompts() { targetNode.val.widgets.find((x) => x.name === "image_prompts_json").value = JSON.stringify(imagePromptsMulti.val); - const canvas = document.getElementById("mask-canvas"); - const base64Image = canvas.toDataURL(); - api.fetchApi("/segments", { - method: "POST", - body: JSON.stringify({ - name: embeddingID.val, - segments: { - [selectedLayer.val]: base64Image, - }, - }), - }); + // const canvas = document.getElementById("mask-canvas"); + // const base64Image = canvas.toDataURL(); + // api.fetchApi("/segments", { + // method: "POST", + // body: JSON.stringify({ + // name: embeddingID.val, + // segments: { + // [selectedLayer.val]: base64Image, + // }, + // }), + // }); } else { targetNode.val.widgets.find((x) => x.name === "image_prompts_json").value = JSON.stringify(imagePrompts.val); @@ -47,6 +48,38 @@ export function updateImagePrompts() { targetNode.val.graph.change(); } +export async function uploadSegments() { + const emptyLayers = []; + Object.entries(imagePromptsMulti.val).forEach(([key, value]) => { + if (value.length === 0) { + emptyLayers.push(key); + } + }); + if (emptyLayers.length > 0) { + alertDialog.val = { + text: "The following layers have no segments: " + emptyLayers.join(", "), + time: 5000, + }; + return false; + } + + const segments = {}; + for (const [layer, prompts] of Object.entries(imagePromptsMulti.val)) { + await drawSegment(getClicks(prompts)); + const canvas = document.getElementById("mask-canvas"); + const base64Image = canvas.toDataURL(); + segments[layer] = base64Image; + } + await api.fetchApi("/segments", { + method: "POST", + body: JSON.stringify({ + name: embeddingID.val, + segments, + }), + }); + return true; +} + async function handleClick(e) { const rect = e.target.getBoundingClientRect(); const x = e.clientX - rect.left; @@ -87,8 +120,8 @@ function handleImageSize(image) { return { height: h, width: w, samScale, imgScale }; } -export function getClicks() { - return imagePrompts.val.map((point) => ({ +export function getClicks(prompts) { + return (prompts || imagePrompts.val).map((point) => ({ x: point.x, y: point.y, clickType: point.label, @@ -136,13 +169,13 @@ export function LayerEditor() { onclick: () => { console.log("close"); showImageEditor.val = false; - api.fetchApi("/segments_order", { - method: "POST", - body: JSON.stringify({ - name: embeddingID.val, - order: Object.keys(imagePromptsMulti.val), - }), - }); + // api.fetchApi("/segments_order", { + // method: "POST", + // body: JSON.stringify({ + // name: embeddingID.val, + // order: Object.keys(imagePromptsMulti.val), + // }), + // }); }, }, span({ diff --git a/routes.py b/routes.py index ee9a924..d2579a8 100644 --- a/routes.py +++ b/routes.py @@ -111,25 +111,30 @@ async def post_segments(request): post = await request.json() name = post.get("name") segments = post.get("segments") - os.makedirs(os.path.join(folder_paths.base_path, f"output/{name}"), exist_ok=True) - for key, value in segments.items(): - filename = os.path.join(folder_paths.base_path, f"output/{name}/{key}.png") - with open(filename, "wb") as f: - f.write(base64.b64decode(value.split(",")[1])) - return web.json_response({}) - - -@server.PromptServer.instance.routes.post("/segments_order") -async def post_segments(request): - post = await request.json() - name = post.get("name") - order = post.get("order") output_dir = os.path.join(folder_paths.base_path, f"output/{name}") os.makedirs(output_dir, exist_ok=True) + for key, value in segments.items(): + filename = os.path.join(output_dir, f"{key}.png") + with open(filename, "wb") as f: + f.write(base64.b64decode(value.split(",")[1])) + + order = list(segments.keys()) with open(os.path.join(output_dir, "order.json") , "w") as f: json.dump(order, f) return web.json_response({}) + +# @server.PromptServer.instance.routes.post("/segments_order") +# async def post_segments(request): +# post = await request.json() +# name = post.get("name") +# order = post.get("order") +# output_dir = os.path.join(folder_paths.base_path, f"output/{name}") +# os.makedirs(output_dir, exist_ok=True) +# with open(os.path.join(output_dir, "order.json") , "w") as f: +# json.dump(order, f) +# return web.json_response({}) + @server.PromptServer.instance.routes.get("/get_webhook") async def get_webhook(request): url = os.getenv('DISCORD_WEBHOOK_URL') From 718e6b78072c9bc4ef2fe19bf35bdd18f251934e Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 14 Nov 2023 12:43:28 +0800 Subject: [PATCH 030/127] fix: segment order file not found --- js/AvatarPreview.js | 6 +++--- js/LayerEditor.js | 3 ++- js/index.js | 17 +++++++++-------- sam/sam_multilayer.py | 6 +++++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 9381e1a..d437640 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -11,7 +11,7 @@ const { button, iframe, div, img, input, label, span } = van.tags; import { app } from "./app.js"; import { uploadPreview } from "./index.js"; import { api } from "./api.js"; -import { uploadSegments } from "./LayerEditor.js"; +// import { uploadSegments } from "./LayerEditor.js"; async function loadJSONWorkflow() { const json = await (await fetch("./get_default_workflow")).json(); @@ -169,8 +169,8 @@ export function AvatarPreview() { class: () => "btn w-96 normal-case " + (stage.val < 2 ? "btn-disabled" : ""), onclick: async () => { - const uploaded = await uploadSegments(); - if (!uploaded) return; + // const uploaded = await uploadSegments(); + // if (!uploaded) return; const graph = app.graph; const imageNodes = graph.findNodesByType("LoadImage"); diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 675ec12..894d8aa 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -166,9 +166,10 @@ export function LayerEditor() { { class: () => "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-2 top-0 z-[200] w-fit", - onclick: () => { + onclick: async () => { console.log("close"); showImageEditor.val = false; + await uploadSegments(); // api.fetchApi("/segments_order", { // method: "POST", // body: JSON.stringify({ diff --git a/js/index.js b/js/index.js index 1f3542e..3aa93d9 100644 --- a/js/index.js +++ b/js/index.js @@ -453,7 +453,7 @@ const ext = { window.addEventListener( "keydown", - (event) => { + async (event) => { if (event.key === "Escape") { event.preventDefault(); if (my_modal_3.open) { @@ -461,13 +461,14 @@ const ext = { } else { showImageEditor.val = false; showEditor.val = false; - api.fetchApi("/segments_order", { - method: "POST", - body: JSON.stringify({ - name: embeddingID.val, - order: Object.keys(imagePromptsMulti.val), - }), - }); + await uploadSegments(); + // api.fetchApi("/segments_order", { + // method: "POST", + // body: JSON.stringify({ + // name: embeddingID.val, + // order: Object.keys(imagePromptsMulti.val), + // }), + // }); } } }, diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 35a80a6..8f1e2b2 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -103,8 +103,12 @@ class SAMMultiLayer: image_prompts = json.loads(image_prompts_json) result = [image_prompts] + + order_file = f"{self.output_dir}/{embedding_id}/order.json" + if not os.path.exists(order_file): + raise FileNotFoundError("Segments order file not found. Please click the 'Edit prompt' in the graph first.") - with open(f"{self.output_dir}/{embedding_id}/order.json") as f: + with open(order_file) as f: order = json.load(f) for segment in order: From 66999ed3e51fb49d6dbd0435cd7eb95580732b3a Mon Sep 17 00:00:00 2001 From: San Date: Tue, 14 Nov 2023 13:40:49 +0800 Subject: [PATCH 031/127] new api --- routes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/routes.py b/routes.py index d2579a8..028d7b3 100644 --- a/routes.py +++ b/routes.py @@ -139,3 +139,9 @@ async def post_segments(request): async def get_webhook(request): url = os.getenv('DISCORD_WEBHOOK_URL') return web.json_response(url) + +@server.PromptServer.instance.routes.post("/input_file") +async def post_input_file(request): + post = await request.json() + uuid = post.get("uuid") + return web.json_response({'uuid': uuid}) \ No newline at end of file From 6781e757646aaefbff1facae2caf3f554e405b09 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 14 Nov 2023 13:35:33 +0800 Subject: [PATCH 032/127] fix: onnx url --- js/onnx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/onnx.js b/js/onnx.js index ec5df53..6dae0ae 100644 --- a/js/onnx.js +++ b/js/onnx.js @@ -11,7 +11,7 @@ export let model = null; export const initModel = async (modelType) => { try { model = await ort.InferenceSession.create( - `http://127.0.0.1:8188/sam_model?type=${modelType}` + `${location.protocol}//${location.host}/sam_model?type=${modelType}` ); } catch (e) { console.log(e); From 3cd3b4c854207cf877bc8dda7c38a37ef9961a96 Mon Sep 17 00:00:00 2001 From: San Date: Tue, 14 Nov 2023 16:04:22 +0800 Subject: [PATCH 033/127] test api from gpts --- routes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/routes.py b/routes.py index 028d7b3..c74a8f5 100644 --- a/routes.py +++ b/routes.py @@ -142,6 +142,13 @@ async def get_webhook(request): @server.PromptServer.instance.routes.post("/input_file") async def post_input_file(request): - post = await request.json() - uuid = post.get("uuid") - return web.json_response({'uuid': uuid}) \ No newline at end of file + post = await request.read() + + output_dir = os.path.join(folder_paths.base_path, "input") + os.makedirs(output_dir, exist_ok=True) + + filename = os.path.join(output_dir, "imagefromai.png") + with open(filename, "wb") as f: + f.write(post) + + return web.json_response({}) \ No newline at end of file From 483b234182209c0d946b26cb1edae27919d2883e Mon Sep 17 00:00:00 2001 From: San Date: Tue, 14 Nov 2023 17:30:23 +0800 Subject: [PATCH 034/127] hide sidebar button in layer editor --- js/LayerEditor.js | 14 +++++- js/tw-styles.css | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 894d8aa..d41e610 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -146,6 +146,9 @@ export async function drawSegment(clicks) { export function LayerEditor() { let realTimeSegment = true; + + const showSidebar = van.state(true) + document.addEventListener("keydown", (e) => { if (showImageEditor.val && e.code === "Tab") { e.preventDefault(); @@ -186,6 +189,15 @@ export function LayerEditor() { }), div("Back") ), + button( + { + class: () => + "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-28 top-0 z-[200] w-fit", + onclick: () => showSidebar.val = !showSidebar.val + , + }, + div(() => showSidebar.val ? "Hide UI" : "Show UI") + ), div( { class: @@ -313,6 +325,6 @@ export function LayerEditor() { }) ) ), - SideBar() + () => showSidebar.val ? SideBar() : div() ); } diff --git a/js/tw-styles.css b/js/tw-styles.css index 748e73b..1b39e45 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -998,12 +998,61 @@ html{ border-radius: 0px; } +.join{ + display: inline-flex; + align-items: stretch; + border-radius: var(--rounded-btn, 0.5rem); +} + +.join :where(.join-item){ + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item{ + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item{ + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ border-top-right-radius: inherit; border-bottom-right-radius: inherit; } +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item){ + border-bottom-left-radius: inherit; + border-top-left-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item{ + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item){ + border-top-right-radius: inherit; + border-bottom-right-radius: inherit; +} + +:where(.join *){ + border-radius: inherit; +} + .link{ cursor: pointer; text-decoration-line: underline; @@ -1498,6 +1547,12 @@ details.collapse summary::-webkit-details-marker{ --tw-placeholder-opacity: 0.2; } +.join > :where(*:not(:first-child)){ + margin-top: 0px; + margin-bottom: 0px; + margin-left: -1px; +} + .link:focus{ outline: 2px solid transparent; outline-offset: 2px; @@ -1844,6 +1899,46 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.join.join-vertical{ + flex-direction: column; +} + +.join.join-vertical .join-item:first-child:not(:last-child), + .join.join-vertical *:first-child:not(:last-child) .join-item{ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.join.join-vertical .join-item:last-child:not(:first-child), + .join.join-vertical *:last-child:not(:first-child) .join-item{ + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; +} + +.join.join-horizontal{ + flex-direction: row; +} + +.join.join-horizontal .join-item:first-child:not(:last-child), + .join.join-horizontal *:first-child:not(:last-child) .join-item{ + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: inherit; + border-top-left-radius: inherit; +} + +.join.join-horizontal .join-item:last-child:not(:first-child), + .join.join-horizontal *:last-child:not(:first-child) .join-item{ + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: inherit; + border-top-right-radius: inherit; +} + .modal-bottom{ place-items: end; } @@ -1944,6 +2039,18 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.join.join-vertical > :where(*:not(:first-child)){ + margin-left: 0px; + margin-right: 0px; + margin-top: -1px; +} + +.join.join-horizontal > :where(*:not(:first-child)){ + margin-top: 0px; + margin-bottom: 0px; + margin-left: -1px; +} + .modal-top :where(.modal-box){ width: 100%; max-width: none; @@ -2027,6 +2134,10 @@ details.collapse summary::-webkit-details-marker{ left: 50%; } +.left-12{ + left: 3rem; +} + .left-2{ left: 0.5rem; } @@ -2063,6 +2174,18 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } +.left-24{ + left: 6rem; +} + +.left-32{ + left: 8rem; +} + +.left-28{ + left: 7rem; +} + .z-\[1000\]{ z-index: 1000; } From 68dee8a4321efff85c4c9aa3ad719f752c09eae0 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 04:32:13 -0500 Subject: [PATCH 035/127] feat: multi layer group --- blender/ops_group.py | 3 +- js/index.js | 69 +++++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/blender/ops_group.py b/blender/ops_group.py index 211e8f3..45766f9 100644 --- a/blender/ops_group.py +++ b/blender/ops_group.py @@ -8,5 +8,6 @@ class GroupOps(blender_node.ObjectOps): RETURN_TYPES = (blender_node.BPY_OBJS,) def blender_process(self, bpy, BPY_OBJ, BPY_OBJ2, **props): - return ([BPY_OBJ, BPY_OBJ2],) + prop_values = props.values() + return ([BPY_OBJ, BPY_OBJ2, *prop_values],) diff --git a/js/index.js b/js/index.js index 3aa93d9..b8d80cb 100644 --- a/js/index.js +++ b/js/index.js @@ -73,15 +73,15 @@ LGraphGroup.prototype.repositionNodes = function () { let sortedNodes = this._nodes.sort((a, b) => a.pos[1] - b.pos[1]); // Separate input and output nodes from the rest const inputNodes = sortedNodes.filter( - (node) => node.properties.routeType === "input", + (node) => node.properties.routeType === "input" ); const outputNodes = sortedNodes.filter( - (node) => node.properties.routeType === "output", + (node) => node.properties.routeType === "output" ); const otherNodes = sortedNodes.filter( (node) => node.properties.routeType !== "input" && - node.properties.routeType !== "output", + node.properties.routeType !== "output" ); // Concatenate the arrays so that input nodes are first and output nodes are last @@ -215,7 +215,7 @@ function openInAvatechEditor(url, fileName) { blendshapes: targetNode.val.widgets.find((x) => x.name === "shape_flow") .value, }, - "*", + "*" ); } @@ -288,7 +288,7 @@ function showMyImageEditor(node) { .then(() => { showLoading.val = false; const v = JSON.parse( - node.widgets.find((x) => x.name === "image_prompts_json").value, + node.widgets.find((x) => x.name === "image_prompts_json").value ); if (!Array.isArray(v)) { @@ -305,13 +305,13 @@ function showMyImageEditor(node) { showImageEditor.val = true; imageUrl.val = api.apiURL( `/view?filename=${encodeURIComponent( - connectedImageFileName, - )}&type=input&subfolder=${split.length > 1 ? split[0] : ""}`, + connectedImageFileName + )}&type=input&subfolder=${split.length > 1 ? split[0] : ""}` ); const embeedingUrl = api.apiURL( `/view?filename=${encodeURIComponent( - `${id}_${modelType}.npy`, - )}&type=output&subfolder=`, + `${id}_${modelType}.npy` + )}&type=output&subfolder=` ); loadNpyTensor(embeedingUrl).then((tensor) => { embeddings.val = tensor; @@ -354,7 +354,7 @@ const ext = { targetNode.val = node; openInAvatechEditor( "https://editor.avatech.ai?comfyui=true", - fileName.val, + fileName.val ); // openInAvatechEditor("http://localhost:3006?comfyui=true", fileName.val); }); @@ -387,6 +387,24 @@ const ext = { widget: btn, }; }, + GROUP_OPS(node, inputName, inputData, app) { + const btn = node.addWidget("button", "Add OBJ", "", () => { + node.addInput("BPY_OBJ" + (node.inputs.length + 1), "BPY_OBJ"); + node.graph.change(); + }); + return { + widget: btn, + }; + }, + GROUP_OPS_DELETE(node, inputName, inputData, app) { + const btn = node.addWidget("button", "Delete OBJ", "", () => { + node.removeInput(node.inputs.length - 1); + node.graph.change(); + }); + return { + widget: btn, + }; + }, }; }, @@ -412,13 +430,13 @@ const ext = { window.addEventListener("message", (event) => { if (!event.data.flow || Object.entries(event.data.flow).length <= 0) return; - updateBlendshapesPrompts(event.data.flow); + updateBlendshapesPrompts(event.data.flow); }); api.addEventListener("executed", (evt) => { if (evt.detail?.output.gltfFilename) { const viewer = document.getElementById( - "avatech-viewer-iframe", + "avatech-viewer-iframe" ).contentWindow; const gltfFilename = @@ -446,7 +464,7 @@ const ext = { avatarURL: gltfFilename, blendshapes: evt.detail?.output.SHAPE_FLOW[0], }), - "*", + "*" ); } }); @@ -474,7 +492,7 @@ const ext = { }, { capture: true, - }, + } ); graphCanvas.addEventListener("keydown", (event) => { @@ -550,7 +568,7 @@ const ext = { a.href = url; a.setAttribute( "download", - new URLSearchParams(url.search).get("filename"), + new URLSearchParams(url.search).get("filename") ); document.body.append(a); a.click(); @@ -580,7 +598,7 @@ const ext = { a.href = url; a.setAttribute( "download", - new URLSearchParams(url.search).get("filename"), + new URLSearchParams(url.search).get("filename") ); document.body.append(a); a.click(); @@ -593,7 +611,7 @@ const ext = { callback: () => { openInAvatechEditor( "http://localhost:3006?comfyui=true", - gltfFilename, + gltfFilename ); }, }); @@ -603,7 +621,7 @@ const ext = { callback: () => { openInAvatechEditor( "https://editor.avatech.ai?comfyui=true", - gltfFilename, + gltfFilename ); }, }); @@ -629,6 +647,9 @@ const ext = { nodeData.input.required.obj = ["MESH_GROUP_CONFIG"]; nodeData.input.required.del_obj = ["MESH_GROUP_DELETE"]; break; + case "GroupOps": + nodeData.input.required.obj = ["GROUP_OPS"]; + nodeData.input.required.del_obj = ["GROUP_OPS_DELETE"]; default: break; } @@ -659,7 +680,7 @@ export async function uploadPreview() { infoDialog.show( `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + labData.modelId + - ``, + `` ); previewModelId.val = labData.modelId; } @@ -713,7 +734,7 @@ function injectUIComponentToComfyuimenu() { "https://labs.avatech.ai/api/share?id=" + previewModelId.val, { method: "GET", - }, + } ).then((e) => e.json()); await fetch(labData.url, { @@ -730,13 +751,13 @@ function injectUIComponentToComfyuimenu() { "https://labs.avatech.ai/api/purgecdn?id=" + previewModelId.val, { method: "GET", - }, + } ).catch((error) => console.error(error)); infoDialog.show( `Preview updated: https://editor.avatech.ai/viewer?avatarId=` + labData.modelId + - "\n Remember to hard refresh before checking out the new preview!", + "\n Remember to hard refresh before checking out the new preview!" ); shareLoading.val = false; @@ -751,7 +772,7 @@ function injectUIComponentToComfyuimenu() { event: e, scale: 1.3, }, - window, + window ); menu.root.classList.add("popup"); }; @@ -776,7 +797,7 @@ function injectUIComponentToComfyuimenu() { infoDialog.show( `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + previewModelId.val + - ``, + `` ); } }; From a67c0c9958aa7ef593af21fc6fda8db924d44f86 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 14 Nov 2023 17:47:37 +0800 Subject: [PATCH 036/127] chore: raise error when no contours found --- blender/mesh_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blender/mesh_utils.py b/blender/mesh_utils.py index 2dd6593..3b755e3 100644 --- a/blender/mesh_utils.py +++ b/blender/mesh_utils.py @@ -14,6 +14,9 @@ def genreate_mesh_from_texture(bpy, image): contours, _ = cv2.findContours( gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + if len(contours) == 0: + raise Exception("No contours found. Please ensure that the image has the correct segments (e.g. when you click on the mouth, it should display a proper blue area over the mouth region).") + # Get the largest contour areas = [cv2.contourArea(contour) for contour in contours] From 060eeb3457ab0384006ede2d4e7ed1a233b829ef Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 14 Nov 2023 18:29:10 +0800 Subject: [PATCH 037/127] chore: uncomment backend SAM node --- sam/sam_multilayer.py | 141 +++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 69 deletions(-) diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 8f1e2b2..1549f4d 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -43,80 +43,83 @@ class SAMMultiLayer: FUNCTION = "load_image" def load_image(self, image, ckpt, embedding_id, image_prompts_json): - # global global_predictor - # model_type = re.findall(r'vit_[lbh]', ckpt)[0] - - # if global_predictor is None: - # ckpt = folder_paths.get_full_path("sams", ckpt) - # sam = sam_model_registry[model_type](checkpoint=ckpt) - # predictor = SamPredictor(sam) - # global_predictor = predictor - - # predictor = global_predictor - - # emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" - # if not os.path.exists(emb_filename): - # image_np = (image[0].numpy() * 255).astype(np.uint8) - # predictor.set_image(image_np) - # emb = predictor.get_image_embedding().cpu().numpy() - # np.save(emb_filename, emb) - - # with open(f"{self.output_dir}/{embedding_id}_{model_type}.json", "w") as f: - # data = { - # "input_size": predictor.input_size, - # "original_size": predictor.original_size, - # } - # json.dump(data, f) - # else: - # emb = np.load(emb_filename) - - # with open(f"{self.output_dir}/{embedding_id}_{model_type}.json") as f: - # data = json.load(f) - # predictor.input_size = data["input_size"] - # predictor.features = torch.from_numpy(emb) - # predictor.is_image_set = True - # predictor.original_size = data["original_size"] - - # image_prompts = json.loads(image_prompts_json) - - # result = [image_prompts] - - # if isinstance(image_prompts, list): - # pass - # elif all(isinstance(item, list) for item in image_prompts.values()): - # for item in image_prompts.values(): - # if (len(item) == 0): - # h, w, c = image[0].shape - # result.append(torch.zeros(1, h, w, c)) - # continue - # point_coords = np.array([[p['x'], p['y']] for p in item]) - # point_labels = np.array([p['label'] for p in item]) - - # masks, _, _ = predictor.predict( - # point_coords=point_coords, - # point_labels=point_labels, - # ) - # masks = torch.from_numpy(masks) - # masks = rearrange(masks[0], 'h w -> 1 h w') - # out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image - # result.append(out_image) - image_prompts = json.loads(image_prompts_json) - result = [image_prompts] order_file = f"{self.output_dir}/{embedding_id}/order.json" - if not os.path.exists(order_file): - raise FileNotFoundError("Segments order file not found. Please click the 'Edit prompt' in the graph first.") + if os.path.exists(order_file): + # Frontend uploads segments images to backend => backend reads all segments images and passes them to next nodes + with open(order_file) as f: + order = json.load(f) - with open(order_file) as f: - order = json.load(f) + result = [image_prompts] - for segment in order: - image = Image.open(f"{self.output_dir}/{embedding_id}/{segment}.png") - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - result.append(image) - return result + for segment in order: + image = Image.open(f"{self.output_dir}/{embedding_id}/{segment}.png") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + result.append(image) + + return result + else: + # Frontend uploads clicks coordinates to backend => backend runs SAM and passes the segments to next nodes + global global_predictor + model_type = re.findall(r'vit_[lbh]', ckpt)[0] + + if global_predictor is None: + ckpt = folder_paths.get_full_path("sams", ckpt) + sam = sam_model_registry[model_type](checkpoint=ckpt) + predictor = SamPredictor(sam) + global_predictor = predictor + + predictor = global_predictor + + emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" + if not os.path.exists(emb_filename): + image_np = (image[0].numpy() * 255).astype(np.uint8) + predictor.set_image(image_np) + emb = predictor.get_image_embedding().cpu().numpy() + np.save(emb_filename, emb) + + with open(f"{self.output_dir}/{embedding_id}_{model_type}.json", "w") as f: + data = { + "input_size": predictor.input_size, + "original_size": predictor.original_size, + } + json.dump(data, f) + else: + emb = np.load(emb_filename) + + with open(f"{self.output_dir}/{embedding_id}_{model_type}.json") as f: + data = json.load(f) + predictor.input_size = data["input_size"] + predictor.features = torch.from_numpy(emb) + predictor.is_image_set = True + predictor.original_size = data["original_size"] + + image_prompts = json.loads(image_prompts_json) + + result = [image_prompts] + + if isinstance(image_prompts, list): + pass + elif all(isinstance(item, list) for item in image_prompts.values()): + for item in image_prompts.values(): + if (len(item) == 0): + h, w, c = image[0].shape + result.append(torch.zeros(1, h, w, c)) + continue + point_coords = np.array([[p['x'], p['y']] for p in item]) + point_labels = np.array([p['label'] for p in item]) + + masks, _, _ = predictor.predict( + point_coords=point_coords, + point_labels=point_labels, + ) + masks = torch.from_numpy(masks) + masks = rearrange(masks[0], 'h w -> 1 h w') + out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image + result.append(out_image) + return result NODE_CLASS_MAPPINGS = {"SAM MultiLayer": SAMMultiLayer} From fd8ba2d9ce9010362207170b4e44117b9c126cc9 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 15 Nov 2023 01:36:44 +0800 Subject: [PATCH 038/127] fix: file upload --- js/AvatarPreview.js | 80 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index d437640..8adf785 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -116,8 +116,26 @@ export function AvatarPreview() { class: () => "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", onclick: async () => { - previewImg.val = await uploadImage(); - stage.val = 1; + // previewImg.val = await uploadImage(); + // stage.val = 1; + var input = document.createElement("input"); + input.type = "file"; + + document.body.appendChild(input); + + // when the input content changes, do something + input.onchange = async function (e) { + stage.val = 1; + if (Object.entries(e.target.files).length) { + await uploadFile(e.target.files[0], true); + } + previewImg.val = URL.createObjectURL(e.target.files[0]); + // upload files + document.body.removeChild(input); + }; + + // Trigger file browser + input.click(); }, }, div({ class: "badge badge-neutral" }, "1"), @@ -365,3 +383,61 @@ export function AvatarPreview() { ) ); } + +function showImage(name) { + const graph = app.graph; + const node = graph.findNodesByType("LoadImage"); + const img = new Image(); + img.onload = () => { + node[0].imgs = [img]; + app.graph.setDirtyCanvas(true); + }; + let folder_separator = name.lastIndexOf("/"); + let subfolder = ""; + if (folder_separator > -1) { + subfolder = name.substring(0, folder_separator); + name = name.substring(folder_separator + 1); + } + img.src = api.apiURL( + `/view?filename=${encodeURIComponent( + name + )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}` + ); + node.setSizeForImage?.(); +} + +async function uploadFile(file, updateNode, pasted = false) { + try { + // Wrap file in formdata so it includes filename + const graph = app.graph; + const nodes = graph.findNodesByType("LoadImage"); + const widgets = nodes[0].widgets.find((w) => w.name === "image"); + const body = new FormData(); + body.append("image", file); + if (pasted) body.append("subfolder", "pasted"); + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + // Add the file to the dropdown list and update the widget value + let path = data.name; + if (data.subfolder) path = data.subfolder + "/" + path; + + if (!widgets.options.values.includes(path)) { + widgets.options.values.push(path); + } + + if (updateNode) { + showImage(path); + widgets.value = path; + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } +} From fd8e8e744986313d6e031c548017f2bf6da89248 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 15 Nov 2023 02:12:34 +0800 Subject: [PATCH 039/127] fix: workflow --- js/AvatarPreview.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 8adf785..c9bf283 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -117,24 +117,20 @@ export function AvatarPreview() { "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", onclick: async () => { // previewImg.val = await uploadImage(); - // stage.val = 1; - var input = document.createElement("input"); + stage.val = 1; + let input = document.createElement("input"); input.type = "file"; - + input.accept = "image/jpeg,image/png,image/webp"; document.body.appendChild(input); // when the input content changes, do something input.onchange = async function (e) { - stage.val = 1; if (Object.entries(e.target.files).length) { await uploadFile(e.target.files[0], true); } previewImg.val = URL.createObjectURL(e.target.files[0]); - // upload files document.body.removeChild(input); }; - - // Trigger file browser input.click(); }, }, @@ -302,9 +298,20 @@ export function AvatarPreview() { return button( { class: () => - "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", onclick: () => { - document.getElementById("comfy-load-button").click(); + let input = document.createElement("input"); + input.type = "file"; + document.body.appendChild(input); + input.accept = ".json,image/png,.latent,.safetensors"; + input.onchange = async function (e) { + if (Object.entries(e.target.files).length) { + app.handleFile(e.target.files[0]); + } + document.body.removeChild(input); + }; + input.click(); + // document.getElementById("comfy-load-button").click(); }, }, span({ From e769efcda704c2e57efce2de41ee01149b0c05f2 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 15 Nov 2023 02:18:32 +0800 Subject: [PATCH 040/127] fix: bg transparent --- js/AvatarPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index c9bf283..b45c60f 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -345,7 +345,7 @@ export function AvatarPreview() { return ( (showPreview.val ? "" : "hidden ") + - "w-full h-full absolute left-0 top-0 z-[99] pointer-events-auto flex border-none" + "w-full h-full absolute left-0 top-0 z-[99] pointer-events-auto flex border-none bg-white" ); }, }, From 6aaa159b98e98df3c6e80b808f6e04c611f1bca3 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 13:19:49 -0500 Subject: [PATCH 041/127] fix: image size --- js/AvatarPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index b45c60f..911bf33 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -151,7 +151,7 @@ export function AvatarPreview() { () => previewImg.val != "" ? img({ - class: () => "z-[10] object-contain w-full h-[394px] border", + class: () => "z-[10] object-contain w-[400px] h-[400px] border", src: previewImg, }) : "", From f957bcf4b41ef5493220e82b4f433929e76f8211 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 13:36:49 -0500 Subject: [PATCH 042/127] update workflow --- routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes.py b/routes.py index c74a8f5..496414f 100644 --- a/routes.py +++ b/routes.py @@ -100,7 +100,8 @@ async def post_sam_model(request): async def get_default_workflow(request): # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow # json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" # Benny workflow - json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173536418337914970/newstyle.json?ex=65644ff5&is=6551daf5&hm=f129838fae10197351bd27c69c7ff5eb4edf2c7d6ed74e6db8b55ddaa3c77dee&' # Deepwoo workflow + # json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173536418337914970/newstyle.json?ex=65644ff5&is=6551daf5&hm=f129838fae10197351bd27c69c7ff5eb4edf2c7d6ed74e6db8b55ddaa3c77dee&' # Deepwoo workflow + json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' response = requests.get(json_link) response.raise_for_status() return web.json_response(response.json()) From 2dc693f474f233d6bce1807a3957029d696f3cf1 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Tue, 14 Nov 2023 16:09:35 -0800 Subject: [PATCH 043/127] finish image route link --- js/AvatarPreview.js | 28 ++++++++++++++++++++++++++++ routes.py | 25 ++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 911bf33..3b5b628 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -43,6 +43,31 @@ async function uploadImage() { const jsonWorkflowLoading = van.state(true); +async function prepareImageFromUrlRedirect(stage) { + await new Promise(resolve => setTimeout(resolve, 2000)); + const queue_id = new URLSearchParams(window.location.search).get('queue-id') + if ( + queue_id && queue_id != "" + ) { + console.log(queue_id); + stage.val = 1 + const graph = app.graph; + const node = graph.findNodesByType("LoadImage"); + const imageName = "create_avatar_endpoint_" + queue_id + ".png" + console.log(node[0]); + node[0].widgets_values[0] = imageName + node[0].widgets[0].value = imageName + node[0].widgets[0]._value = imageName + graph.change() + previewImg.val = api.apiURL( + `/view?filename=${encodeURIComponent( + imageName + )}&type=input&subfolder=${""}${app.getPreviewFormatParam()}` + ); + console.log(previewImg); + } +} + export function AvatarPreview() { console.log("getting workflow json now"); loadJSONWorkflow().then(() => { @@ -64,6 +89,9 @@ export function AvatarPreview() { const email = van.state(""); const stage = van.state(0); // 0: upload image, 1: edit segment, 2: generate + // This will wait 2 seconds until the everything is loaded + prepareImageFromUrlRedirect(stage) + const renderSteps = () => { return div( { diff --git a/routes.py b/routes.py index 496414f..b8cd78d 100644 --- a/routes.py +++ b/routes.py @@ -152,4 +152,27 @@ async def post_input_file(request): with open(filename, "wb") as f: f.write(post) - return web.json_response({}) \ No newline at end of file + return web.json_response({}) + +import uuid + +@server.PromptServer.instance.routes.post("/create_avatar_from_image") +async def post_input_file(request): + post = await request.read() + + # Doesn't seems working when file isnt png / or nothing is uploaded + if not post: + raise web.HTTPBadRequest(reason="No image data received") + + queue_id = uuid.uuid4() + + output_dir = os.path.join(folder_paths.base_path, "input") + os.makedirs(output_dir, exist_ok=True) + + filename = os.path.join(output_dir, "create_avatar_endpoint_" + str(queue_id) + ".png") + with open(filename, "wb") as f: + f.write(post) + + return web.json_response({ + "redirect_url": "https://ai-assistant.avatech.ai?queue-id=" + str(queue_id) + }) \ No newline at end of file From 1e8187530e2f3fe3c1e20576a76ecef2e656834a Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 14 Nov 2023 19:35:40 -0500 Subject: [PATCH 044/127] fix folder and path --- js/AvatarPreview.js | 35 +++++++++++++++++++++++++++++------ routes.py | 29 ++++++++++++++++++----------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 3b5b628..fbdabcc 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -53,7 +53,7 @@ async function prepareImageFromUrlRedirect(stage) { stage.val = 1 const graph = app.graph; const node = graph.findNodesByType("LoadImage"); - const imageName = "create_avatar_endpoint_" + queue_id + ".png" + const imageName = queue_id + ".png" console.log(node[0]); node[0].widgets_values[0] = imageName node[0].widgets[0].value = imageName @@ -62,7 +62,7 @@ async function prepareImageFromUrlRedirect(stage) { previewImg.val = api.apiURL( `/view?filename=${encodeURIComponent( imageName - )}&type=input&subfolder=${""}${app.getPreviewFormatParam()}` + )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}` ); console.log(previewImg); } @@ -218,6 +218,9 @@ export function AvatarPreview() { const imageNodes = graph.findNodesByType("LoadImage"); if (!imageNodes[0].imgs) return; document.getElementById("queue-button").click(); + console.log("scrolling???"); + window.scrollTo({ top: 0, behavior: "smooth" }); + console.log("hi???"); }, }, div({ class: "badge badge-neutral" }, "3"), @@ -309,7 +312,7 @@ export function AvatarPreview() { return button( { class: () => - "btn flex flex-row btn-ghost text-black normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + "btn flex flex-row btn-ghost text-black normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", onclick: () => { showPreview.val = false; }, @@ -322,11 +325,28 @@ export function AvatarPreview() { ); }; + const renderRestartButton = () => { + return button( + { + class: () => + "btn flex flex-row btn-ghost text-black normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", + onclick: () => { + fetch("https://7a49f4ad27be4dcf.ngrok.app/restart"); + }, + }, + span({ + class: "iconify text-lg", + "data-icon": "mdi:restart", + "data-inline": "false", + }) + ); + }; + const renderChangeWorkflowButton = () => { return button( { class: () => - "btn text-black flex flex-row btn-ghost normal-case px-4 rounded-md left-0 top-0 z-[200] w-fit pointer-events-auto ", + "btn text-black flex flex-row btn-ghost normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", onclick: () => { let input = document.createElement("input"); input.type = "file"; @@ -347,7 +367,9 @@ export function AvatarPreview() { "data-icon": "ic:round-swap-vert", "data-inline": "false", }), - () => (jsonWorkflowLoading.val ? "Loading" : "Change workflow") + span({ class: "sm:flex hidden" }, () => + jsonWorkflowLoading.val ? "Loading" : "Change workflow" + ) ); }; @@ -355,7 +377,7 @@ export function AvatarPreview() { return button( { class: () => - "absolute top-4 right-4 btn text-black btn-ghost w-24 text-xs !px-0 normal-case", + "absolute top-4 right-4 btn sm:w-10 w-32 text-black btn-ghost text-xs !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, "Twitter" @@ -384,6 +406,7 @@ export function AvatarPreview() { class: "absolute top-4 left-4 flex flex-row gap-2", }, renderCloseButton(), + renderRestartButton(), renderChangeWorkflowButton() ), renderTwitter(), diff --git a/routes.py b/routes.py index b8cd78d..52d3e7d 100644 --- a/routes.py +++ b/routes.py @@ -164,15 +164,22 @@ async def post_input_file(request): if not post: raise web.HTTPBadRequest(reason="No image data received") - queue_id = uuid.uuid4() + try: + queue_id = uuid.uuid4() - output_dir = os.path.join(folder_paths.base_path, "input") - os.makedirs(output_dir, exist_ok=True) - - filename = os.path.join(output_dir, "create_avatar_endpoint_" + str(queue_id) + ".png") - with open(filename, "wb") as f: - f.write(post) - - return web.json_response({ - "redirect_url": "https://ai-assistant.avatech.ai?queue-id=" + str(queue_id) - }) \ No newline at end of file + output_dir = os.path.join(folder_paths.base_path, "input","create_avatar_endpoint") + os.makedirs(output_dir, exist_ok=True) + + filename = os.path.join(output_dir,str(queue_id) + ".png") + with open(filename, "wb") as f: + f.write(post) + + return web.json_response({ + "redirect_url": "https://ai-assistant.avatech.ai?queue-id=" + str(queue_id) + }) + except Exception as e: + print(e) + return web.json_response({ + "error": e + }) + From 9f393c7c8020406462fcaa5e441c41ae1c252212 Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 15 Nov 2023 16:23:59 +0800 Subject: [PATCH 045/127] feat: SAM node supports real-time generated image as input --- js/index.js | 27 ++++++++++++++++++++++----- routes.py | 7 +++++-- sam/sam_multilayer.py | 4 ++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/js/index.js b/js/index.js index b8d80cb..e9b12ec 100644 --- a/js/index.js +++ b/js/index.js @@ -26,6 +26,8 @@ import "https://code.iconify.design/3/3.1.0/iconify.min.js"; import { drawSegment, getClicks } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; +export let generatedImagePath = null; + const stylesheet = document.createElement("link"); stylesheet.setAttribute("type", "text/css"); stylesheet.setAttribute("rel", "stylesheet"); @@ -243,6 +245,10 @@ function getInputWidgetValue(node, inputIndex, widgetName) { console.log(targetLink, nodea); console.log(nodea.getInputNode(0, true)); + if (nodea.type !== "IMAGE") { + return; + } + /** @type {string} */ return nodea.widgets.find((x) => x.name === widgetName).value; } @@ -252,7 +258,8 @@ function getInputWidgetValue(node, inputIndex, widgetName) { * @param {LGraphNode} node */ function showMyImageEditor(node) { - let connectedImageFileName = getInputWidgetValue(node, 0, "image"); + let connectedImageFileName = + getInputWidgetValue(node, 0, "image") || generatedImagePath; if (!connectedImageFileName) { alertDialog.val = { text: "Please connect an image first", @@ -303,10 +310,12 @@ function showMyImageEditor(node) { imagePrompts.val = v; } showImageEditor.val = true; + const isGeneratedImage = connectedImageFileName.startsWith("avatar"); + const subfolder = isGeneratedImage || split.length == 0 ? "" : split[0]; imageUrl.val = api.apiURL( - `/view?filename=${encodeURIComponent( - connectedImageFileName - )}&type=input&subfolder=${split.length > 1 ? split[0] : ""}` + `/view?filename=${encodeURIComponent(connectedImageFileName)}&type=${ + isGeneratedImage ? "output" : "input" + }&subfolder=${subfolder}` ); const embeedingUrl = api.apiURL( `/view?filename=${encodeURIComponent( @@ -430,10 +439,18 @@ const ext = { window.addEventListener("message", (event) => { if (!event.data.flow || Object.entries(event.data.flow).length <= 0) return; - updateBlendshapesPrompts(event.data.flow); + updateBlendshapesPrompts(event.data.flow); }); api.addEventListener("executed", (evt) => { + const images = evt.detail?.output.images; + if ( + images?.length > 0 && + images[0].type === "output" && + images[0].filename.startsWith("avatar") + ) { + generatedImagePath = images[0].filename; + } if (evt.detail?.output.gltfFilename) { const viewer = document.getElementById( "avatech-viewer-iframe" diff --git a/routes.py b/routes.py index 52d3e7d..522c6bc 100644 --- a/routes.py +++ b/routes.py @@ -59,7 +59,10 @@ async def get_sam_model(request): def load_image(image): - image_path = folder_paths.get_annotated_filepath(image) + if image.startswith("avatar"): + image_path = f"{folder_paths.get_output_directory()}/{image}" + else: + image_path = folder_paths.get_annotated_filepath(image) i = Image.open(image_path) i = ImageOps.exif_transpose(i) image = i.convert("RGB") @@ -112,7 +115,7 @@ async def post_segments(request): post = await request.json() name = post.get("name") segments = post.get("segments") - output_dir = os.path.join(folder_paths.base_path, f"output/{name}") + output_dir = os.path.join(folder_paths.base_path, f"output/segments_{name}") os.makedirs(output_dir, exist_ok=True) for key, value in segments.items(): filename = os.path.join(output_dir, f"{key}.png") diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 1549f4d..a14bbe9 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -45,7 +45,7 @@ class SAMMultiLayer: def load_image(self, image, ckpt, embedding_id, image_prompts_json): image_prompts = json.loads(image_prompts_json) - order_file = f"{self.output_dir}/{embedding_id}/order.json" + order_file = f"{self.output_dir}/segments_{embedding_id}/order.json" if os.path.exists(order_file): # Frontend uploads segments images to backend => backend reads all segments images and passes them to next nodes with open(order_file) as f: @@ -54,7 +54,7 @@ class SAMMultiLayer: result = [image_prompts] for segment in order: - image = Image.open(f"{self.output_dir}/{embedding_id}/{segment}.png") + image = Image.open(f"{self.output_dir}/segments_{embedding_id}/{segment}.png") image = np.array(image).astype(np.float32) / 255.0 image = torch.from_numpy(image)[None,] result.append(image) From e408ac2dbee6894be8a0dd425d1ced64952f539b Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 17:12:48 +0800 Subject: [PATCH 046/127] tab for switching flow --- routes.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/routes.py b/routes.py index 522c6bc..9391ec6 100644 --- a/routes.py +++ b/routes.py @@ -146,14 +146,19 @@ async def get_webhook(request): @server.PromptServer.instance.routes.post("/input_file") async def post_input_file(request): - post = await request.read() + post = await request.json() + image_url = post.get("image_url") - output_dir = os.path.join(folder_paths.base_path, "input") + input_dir = os.path.join(folder_paths.base_path, "input") os.makedirs(output_dir, exist_ok=True) - filename = os.path.join(output_dir, "imagefromai.png") + filename = os.path.join(input_dir, "imagefromai.png") + + response = requests.get(image_url) + response.raise_for_status() + with open(filename, "wb") as f: - f.write(post) + f.write(response.content) # Save the image from the URL to the output directory return web.json_response({}) From 7c8005dae6afed9b4c697144c23c46b036d3f7dd Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 17:13:09 +0800 Subject: [PATCH 047/127] twerk --- js/AvatarPreview.js | 346 ++++++++++++++++++++++++++++++-------------- package.json | 2 +- pnpm-lock.yaml | 24 +-- 3 files changed, 252 insertions(+), 120 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index fbdabcc..e886c72 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -95,21 +95,21 @@ export function AvatarPreview() { const renderSteps = () => { return div( { - class: () => "flex flex-col bg-white justify-center", + class: () => "flex flex-col bg-white justify-center w-[32rem]", }, div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1" + "Avatech v1", ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone" + "Get your DALLE3 AI Personal Clone", ), // input({ // type: "file", @@ -139,99 +139,231 @@ export function AvatarPreview() { class: () => " w-full flex flex-col justify-center items-center gap-4", }, - button( - { - class: () => - "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", - onclick: async () => { - // previewImg.val = await uploadImage(); - stage.val = 1; - let input = document.createElement("input"); - input.type = "file"; - input.accept = "image/jpeg,image/png,image/webp"; - document.body.appendChild(input); - - // when the input content changes, do something - input.onchange = async function (e) { - if (Object.entries(e.target.files).length) { - await uploadFile(e.target.files[0], true); - } - previewImg.val = URL.createObjectURL(e.target.files[0]); - document.body.removeChild(input); - }; - input.click(); - }, - }, - div({ class: "badge badge-neutral" }, "1"), - div("Upload your image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", + div( + { class: () => "tabs tabs-lifted tabs-lg w-full" }, + input({ + type: "radio", + name: "my_tabs_1", + class: () => "tab w-full", + checked: true, + ariaLabel: "Custom avatar", }), - () => - previewImgLoading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "" + div( + { class: () => "tab-content text-black w-full" }, + div( + { + class: () => "flex flex-col justify-center items-center gap-4", + }, + button( + { + class: () => + "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + onclick: async () => { + // previewImg.val = await uploadImage(); + // stage.val = 1; + var input = document.createElement("input"); + input.type = "file"; + + document.body.appendChild(input); + + // when the input content changes, do something + input.onchange = async function (e) { + stage.val = 1; + if (Object.entries(e.target.files).length) { + await uploadFile(e.target.files[0], true); + } + previewImg.val = URL.createObjectURL(e.target.files[0]); + // upload files + document.body.removeChild(input); + }; + + // Trigger file browser + input.click(); + }, + }, + div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }), + () => + previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-md", + }) + : "", + ), + () => + previewImg.val != "" + ? img({ + class: () => + "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }) + : "", + button( + { + class: () => + "btn w-full normal-case " + + (stage.val < 1 ? "btn-disabled" : ""), + onclick: () => { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + + const nodes = graph.findNodesByType("SAM MultiLayer"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.type == "button").callback(); + stage.val = 2; + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Edit segment", + ), + button( + { + class: () => + "btn w-full normal-case " + + (stage.val < 2 ? "btn-disabled" : ""), + onclick: async () => { + // const uploaded = await uploadSegments(); + // if (!uploaded) return; + + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + document.getElementById("queue-button").click(); + }, + }, + div({ class: "badge badge-neutral" }, "3"), + () => + loading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Generate", + ), + ), + ), + input({ + type: "radio", + name: "my_tabs_1", + class: () => "tab", + ariaLabel: "Generate avatar", + }), + div( + { class: () => "tab-content text-black w-full" }, + div( + { + class: () => "flex flex-col justify-center items-center gap-4", + }, + button( + { + class: () => + "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + onclick: async () => { + // previewImg.val = await uploadImage(); + // stage.val = 1; + var input = document.createElement("input"); + input.type = "file"; + + document.body.appendChild(input); + + // when the input content changes, do something + input.onchange = async function (e) { + stage.val = 1; + if (Object.entries(e.target.files).length) { + await uploadFile(e.target.files[0], true); + } + previewImg.val = URL.createObjectURL(e.target.files[0]); + // upload files + document.body.removeChild(input); + }; + + // Trigger file browser + input.click(); + }, + }, + div({ class: "badge badge-neutral" }, "1"), + div("Generate image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }), + () => + previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-md", + }) + : "", + ), + () => + previewImg.val != "" + ? img({ + class: () => + "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }) + : "", + button( + { + class: () => + "btn w-full normal-case " + + (stage.val < 1 ? "btn-disabled" : ""), + onclick: () => { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + + const nodes = graph.findNodesByType("SAM MultiLayer"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.type == "button").callback(); + stage.val = 2; + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Auto segment", + ), + button( + { + class: () => + "btn w-full normal-case " + + (stage.val < 2 ? "btn-disabled" : ""), + onclick: async () => { + // const uploaded = await uploadSegments(); + // if (!uploaded) return; + + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + document.getElementById("queue-button").click(); + }, + }, + div({ class: "badge badge-neutral" }, "3"), + () => + loading.val + ? span({ + class: "loading loading-spinner loading-lg", + }) + : "Generate", + ), + ), + ), ), - () => - previewImg.val != "" - ? img({ - class: () => "z-[10] object-contain w-[400px] h-[400px] border", - src: previewImg, - }) - : "", - button( - { - class: () => - "btn w-96 normal-case " + (stage.val < 1 ? "btn-disabled" : ""), - onclick: () => { - /** @type {import('../../../web/types/litegraph.js').LGraph}*/ - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - - const nodes = graph.findNodesByType("SAM MultiLayer"); - - /** @type {any[]}*/ - const widgets = nodes[0].widgets; - console.log(nodes[0]); - console.log(nodes[0].widgets); - widgets.find((x) => x.type == "button").callback(); - stage.val = 2; - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit segment" - ), - button( - { - class: () => - "btn w-96 normal-case " + (stage.val < 2 ? "btn-disabled" : ""), - onclick: async () => { - // const uploaded = await uploadSegments(); - // if (!uploaded) return; - - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - document.getElementById("queue-button").click(); - console.log("scrolling???"); - window.scrollTo({ top: 0, behavior: "smooth" }); - console.log("hi???"); - }, - }, - div({ class: "badge badge-neutral" }, "3"), - () => - loading.val - ? span({ - class: "loading loading-spinner loading-lg", - }) - : "Generate" - ) - ) + ), ); }; @@ -261,7 +393,7 @@ export function AvatarPreview() { class: () => "w-full flex justify-center font-bold italic text-gray-500", }, - span("We are launching OpenAI Assistant API integration soon!") + span("We are launching OpenAI Assistant API integration soon!"), ), div( { class: () => "w-[24rem] flex justify-center items-center" }, @@ -302,9 +434,9 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-lg", }) - : "Get Avatar Link" - ) - ) + : "Get Avatar Link", + ), + ), ); }; @@ -321,7 +453,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "ic:round-close", "data-inline": "false", - }) + }), ); }; @@ -380,7 +512,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn sm:w-10 w-32 text-black btn-ghost text-xs !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter" + "Twitter", ); }; @@ -420,7 +552,7 @@ export function AvatarPreview() { }, renderIFrame(), renderSteps(), - renderShareLink() + renderShareLink(), ); } else { return div( @@ -433,12 +565,12 @@ export function AvatarPreview() { div( { class: () => "flex flex-col" }, renderIFrame(), - renderShareLink() - ) + renderShareLink(), + ), ); } - } - ) + }, + ), ); } @@ -458,8 +590,8 @@ function showImage(name) { } img.src = api.apiURL( `/view?filename=${encodeURIComponent( - name - )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}` + name, + )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`, ); node.setSizeForImage?.(); } diff --git a/package.json b/package.json index 59a6719..91641bb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "ISC", "devDependencies": { "chokidar": "^3.5.3", - "daisyui": "^3.7.5", + "daisyui": "^4.0.7", "tailwindcss": "^3.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebd4edd..4721abd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ devDependencies: specifier: ^3.5.3 version: 3.5.3 daisyui: - specifier: ^3.7.5 - version: 3.7.5 + specifier: ^4.0.7 + version: 4.0.7(postcss@8.4.29) tailwindcss: specifier: ^3.3.3 version: 3.3.3 @@ -132,10 +132,6 @@ packages: fsevents: 2.3.3 dev: true - /colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true - /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -158,17 +154,21 @@ packages: hasBin: true dev: true - /daisyui@3.7.5: - resolution: {integrity: sha512-udhiBJYVvcPGXa+mL5IElke6EdddKecjbbz6m43+9IVqzK8GBwetg092Edclo42TkNboLD9nzodeesJygqZt2A==} + /culori@3.2.0: + resolution: {integrity: sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /daisyui@4.0.7(postcss@8.4.29): + resolution: {integrity: sha512-D84DnNDZKcamwNsxCMrwYaddyz5kC6VO6oe30nM1x67GzCAfarfd3Ar1rpLGXCIqSsEoNZUHO8EcXvX93W2ZkA==} engines: {node: '>=16.9.0'} dependencies: - colord: 2.9.3 css-selector-tokenizer: 0.8.0 - postcss: 8.4.29 + culori: 3.2.0 + picocolors: 1.0.0 postcss-js: 4.0.1(postcss@8.4.29) - tailwindcss: 3.3.3 transitivePeerDependencies: - - ts-node + - postcss dev: true /didyoumean@1.2.2: From df9085adef03cf3b3c042b5e7f45eee6b1c450e5 Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 17:15:18 +0800 Subject: [PATCH 048/127] revert routes change of gpts --- routes.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/routes.py b/routes.py index 9391ec6..31c5755 100644 --- a/routes.py +++ b/routes.py @@ -144,24 +144,6 @@ async def get_webhook(request): url = os.getenv('DISCORD_WEBHOOK_URL') return web.json_response(url) -@server.PromptServer.instance.routes.post("/input_file") -async def post_input_file(request): - post = await request.json() - image_url = post.get("image_url") - - input_dir = os.path.join(folder_paths.base_path, "input") - os.makedirs(output_dir, exist_ok=True) - - filename = os.path.join(input_dir, "imagefromai.png") - - response = requests.get(image_url) - response.raise_for_status() - - with open(filename, "wb") as f: - f.write(response.content) # Save the image from the URL to the output directory - - return web.json_response({}) - import uuid @server.PromptServer.instance.routes.post("/create_avatar_from_image") From 57565169d4b2d91790916ee6a1ba41b122c5c6c0 Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 17:17:56 +0800 Subject: [PATCH 049/127] tailwind style update --- js/tw-styles.css | 1078 ++++++++++++++++++++++++++++++---------------- 1 file changed, 716 insertions(+), 362 deletions(-) diff --git a/js/tw-styles.css b/js/tw-styles.css index 1b39e45..37a597f 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -438,8 +438,60 @@ video { :root, [data-theme]{ - background-color: hsl(var(--b1) / var(--tw-bg-opacity, 1)); - color: hsl(var(--bc) / var(--tw-text-opacity, 1)); + background-color: var(--fallback-b1,oklch(var(--b1)/1)); + color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +@supports not (color: oklch(0 0 0)){ + :root{ + color-scheme: light; + --fallback-p: #491eff; + --fallback-pc: #d4dbff; + --fallback-s: #ff41c7; + --fallback-sc: #fff9fc; + --fallback-a: #00cfbd; + --fallback-ac: #00100d; + --fallback-n: #2b3440; + --fallback-nc: #d7dde4; + --fallback-b1: #ffffff; + --fallback-b2: #e5e6e6; + --fallback-b3: #e5e6e6; + --fallback-bc: #1f2937; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + + @media (prefers-color-scheme: dark){ + :root{ + color-scheme: dark; + --fallback-p: #7582ff; + --fallback-pc: #050617; + --fallback-s: #ff71cf; + --fallback-sc: #190211; + --fallback-a: #00c7b5; + --fallback-ac: #000e0c; + --fallback-n: #2a323c; + --fallback-nc: #a6adbb; + --fallback-b1: #1d232a; + --fallback-b2: #191e24; + --fallback-b3: #15191e; + --fallback-bc: #a6adbb; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + } } html{ @@ -448,40 +500,35 @@ html{ :root{ color-scheme: light; - --pf: 259 94% 44%; - --sf: 314 100% 40%; - --af: 174 75% 39%; - --nf: 214 20% 14%; - --in: 198 93% 60%; - --su: 158 64% 52%; - --wa: 43 96% 56%; - --er: 0 91% 71%; - --inc: 198 100% 12%; - --suc: 158 100% 10%; - --wac: 43 100% 11%; - --erc: 0 100% 14%; + --in: 0.7206 0.191 231.6; + --su: 0.7441 0.213 164.75; + --wa: 0.8471 0.199 83.87; + --er: 0.7176 0.221 22.18; + --pc: 0.89824 0.06192 275.75; + --ac: 0.15352 0.0368 183.61; + --inc: 0 0 0; + --suc: 0 0 0; + --wac: 0 0 0; + --erc: 0 0 0; --rounded-box: 1rem; --rounded-btn: 0.5rem; --rounded-badge: 1.9rem; --animation-btn: 0.25s; --animation-input: .2s; - --btn-text-case: uppercase; --btn-focus-scale: 0.95; --border-btn: 1px; --tab-border: 1px; --tab-radius: 0.5rem; - --p: 259 94% 51%; - --pc: 259 96% 91%; - --s: 314 100% 47%; - --sc: 314 100% 91%; - --a: 174 75% 46%; - --ac: 174 75% 11%; - --n: 214 20% 21%; - --nc: 212 19% 87%; - --b1: 0 0% 100%; - --b2: 0 0% 95%; - --b3: 180 2% 90%; - --bc: 215 28% 17%; + --p: 0.4912 0.3096 275.75; + --s: 0.6971 0.329 342.55; + --sc: 0.9871 0.0106 342.55; + --a: 0.7676 0.184 183.61; + --n: 0.321785 0.02476 255.701624; + --nc: 0.894994 0.011585 252.096176; + --b1: 1 0 0; + --b2: 0.961151 0 0; + --b3: 0.924169 0.00108 197.137559; + --bc: 0.278078 0.029596 256.847952; } *, ::before, ::after{ @@ -584,6 +631,36 @@ html{ --tw-backdrop-sepia: ; } +.alert{ + display: grid; + width: 100%; + grid-auto-flow: row; + align-content: flex-start; + align-items: center; + justify-items: center; + gap: 1rem; + text-align: center; + border-radius: var(--rounded-box, 1rem); + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + padding: 1rem; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --alert-bg: var(--fallback-b2,oklch(var(--b2)/1)); + --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1)); + background-color: var(--alert-bg); +} + +@media (min-width: 640px){ + .alert{ + grid-auto-flow: column; + grid-template-columns: auto minmax(auto,1fr); + justify-items: start; + text-align: start; + } +} + .avatar{ position: relative; display: inline-flex; @@ -625,34 +702,46 @@ html{ width: fit-content; padding-left: 0.563rem; padding-right: 0.563rem; + border-radius: var(--rounded-badge, 1.9rem); border-width: 1px; --tw-border-opacity: 1; - border-color: hsl(var(--b2) / var(--tw-border-opacity)); + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - border-radius: var(--rounded-badge, 1.9rem); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } @media (hover:hover){ .label a:hover{ --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } .menu li > *:not(ul):not(.menu-title):not(details):active, .menu li > *:not(ul):not(.menu-title):not(details).active, .menu li > details > summary:active{ --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-text-opacity: 1; - color: hsl(var(--nc) / var(--tw-text-opacity)); + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + } + + .tab:hover{ + --tw-text-opacity: 1; + } + + .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, + .tabs-boxed :is(input:checked):hover{ + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); } } .btn{ display: inline-flex; + height: 3rem; + min-height: 3rem; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -661,34 +750,29 @@ html{ flex-wrap: wrap; align-items: center; justify-content: center; - border-color: transparent; - border-color: hsl(var(--b2) / var(--tw-border-opacity)); - text-align: center; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 200ms; border-radius: var(--rounded-btn, 0.5rem); - height: 3rem; + border-color: transparent; + border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)); padding-left: 1rem; padding-right: 1rem; - min-height: 3rem; + text-align: center; font-size: 0.875rem; line-height: 1em; gap: 0.5rem; font-weight: 600; text-decoration-line: none; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); border-width: var(--border-btn, 1px); animation: button-pop var(--animation-btn, 0.25s) ease-out; - text-transform: var(--btn-text-case, uppercase); - --tw-border-opacity: 1; - --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + transition-property: color, background-color, border-color, opacity, box-shadow, transform; --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); - outline-color: hsl(var(--bc) / 1); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)); } .btn-disabled, @@ -704,18 +788,8 @@ html{ padding: 0px; } -.btn-group > input[type="radio"].btn{ - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.btn-group > input[type="radio"].btn:before{ - content: attr(data-title); -} - -.btn:is(input[type="checkbox"]), -.btn:is(input[type="radio"]){ +:where(.btn:is(input[type="checkbox"])), +:where(.btn:is(input[type="radio"])){ width: auto; -webkit-appearance: none; -moz-appearance: none; @@ -779,6 +853,15 @@ html{ min-height: fit-content; } +.diff{ + position: relative; + display: grid; + width: 100%; + overflow: hidden; + container-type: inline-size; + grid-template-columns: auto 1fr; +} + .divider{ display: flex; flex-direction: row; @@ -792,10 +875,13 @@ html{ .divider:before, .divider:after{ - content: ""; - flex-grow: 1; height: 0.125rem; width: 100%; + flex-grow: 1; + --tw-content: ''; + content: var(--tw-content); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } .dropdown{ @@ -803,7 +889,7 @@ html{ display: inline-block; } -.dropdown > *:focus{ +.dropdown > *:not(summary):focus{ outline: 2px solid transparent; outline-offset: 2px; } @@ -842,16 +928,26 @@ html{ .btn:hover{ --tw-border-opacity: 1; - border-color: hsl(var(--b3) / var(--tw-border-opacity)); + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, + black + ); --tw-bg-opacity: 1; - background-color: hsl(var(--b3) / var(--tw-bg-opacity)); + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, + black + ); } - .btn-neutral:hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--nf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--nf) / var(--tw-bg-opacity)); + @supports not (color: oklch(0 0 0)){ + .btn:hover{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } } .btn.glass:hover{ @@ -860,26 +956,28 @@ html{ } .btn-ghost:hover{ - --tw-border-opacity: 0; - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.2; + border-color: transparent; + } + + @supports (color: oklch(0 0 0)){ + .btn-ghost:hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + } } .btn-disabled:hover, .btn[disabled]:hover, .btn:disabled:hover{ --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-bg-opacity: 0.2; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); --tw-text-opacity: 0.2; } .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ - --tw-border-opacity: 1; - border-color: hsl(var(--pf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); } .dropdown.dropdown-hover:hover .dropdown-content{ @@ -890,12 +988,24 @@ html{ :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ cursor: pointer; - background-color: hsl(var(--bc) / 0.1); --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); outline: 2px solid transparent; outline-offset: 2px; } + + @supports (color: oklch(0 0 0)){ + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + } + } + + .tab[disabled], + .tab[disabled]:hover{ + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } } .dropdown:is(details) summary::-webkit-details-marker{ @@ -905,24 +1015,25 @@ html{ .file-input{ height: 3rem; flex-shrink: 1; - padding-right: 1rem; + -webkit-padding-end: 1rem; + padding-inline-end: 1rem; font-size: 1rem; line-height: 2; line-height: 1.5rem; overflow: hidden; + border-radius: var(--rounded-btn, 0.5rem); border-width: 1px; - border-color: hsl(var(--bc) / var(--tw-border-opacity)); + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); --tw-border-opacity: 0; --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); - border-radius: var(--rounded-btn, 0.5rem); + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); } .file-input::file-selector-button{ - margin-right: 1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; display: inline-flex; - height: 2.875rem; - min-height: 2.875rem; + height: 100%; flex-shrink: 0; cursor: pointer; -webkit-user-select: none; @@ -945,14 +1056,13 @@ html{ transition-duration: 200ms; border-style: solid; --tw-border-opacity: 1; - border-color: hsl(var(--n) / var(--tw-border-opacity)); + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); font-weight: 600; text-transform: uppercase; - text-transform: var(--btn-text-case, uppercase); --tw-text-opacity: 1; - color: hsl(var(--nc) / var(--tw-text-opacity)); + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); text-decoration-line: none; border-width: var(--border-btn, 1px); animation: button-pop var(--animation-btn, 0.25s) ease-out; @@ -979,23 +1089,11 @@ html{ font-size: 1rem; line-height: 2; line-height: 1.5rem; - border-width: 1px; - border-color: hsl(var(--bc) / var(--tw-border-opacity)); - --tw-border-opacity: 0; - --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); border-radius: var(--rounded-btn, 0.5rem); -} - -.input-group > .input{ - isolation: isolate; -} - -.input-group > *, - .input-group > .input, - .input-group > .textarea, - .input-group > .select{ - border-radius: 0px; + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); } .join{ @@ -1005,52 +1103,60 @@ html{ } .join :where(.join-item){ - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 0; + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; } .join .join-item:not(:first-child):not(:last-child), .join *:not(:first-child):not(:last-child) .join-item{ - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 0; + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; } .join .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .join-item{ - border-top-right-radius: 0; - border-bottom-right-radius: 0; + border-start-end-radius: 0; + border-end-end-radius: 0; } .join .dropdown .join-item:first-child:not(:last-child), .join *:first-child:not(:last-child) .dropdown .join-item{ - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; + border-start-end-radius: inherit; + border-end-end-radius: inherit; } .join :where(.join-item:first-child:not(:last-child)), .join :where(*:first-child:not(:last-child) .join-item){ - border-bottom-left-radius: inherit; - border-top-left-radius: inherit; + border-end-start-radius: inherit; + border-start-start-radius: inherit; } .join .join-item:last-child:not(:first-child), .join *:last-child:not(:first-child) .join-item{ - border-bottom-left-radius: 0; - border-top-left-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; } .join :where(.join-item:last-child:not(:first-child)), .join :where(*:last-child:not(:first-child) .join-item){ - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; + border-start-end-radius: inherit; + border-end-end-radius: inherit; } -:where(.join *){ - border-radius: inherit; +@supports not selector(:has(*)){ + :where(.join *){ + border-radius: inherit; + } +} + +@supports selector(:has(*)){ + :where(.join *:has(.join-item)){ + border-radius: inherit; + } } .link{ @@ -1079,8 +1185,10 @@ html{ .menu :where(li ul){ position: relative; white-space: nowrap; - margin-left: 1rem; - padding-left: 0.5rem; + -webkit-margin-start: 1rem; + margin-inline-start: 1rem; + -webkit-padding-start: 0.5rem; + padding-inline-start: 0.5rem; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), @@ -1101,7 +1209,7 @@ html{ -webkit-user-select: none; -moz-user-select: none; user-select: none; - color: hsl(var(--bc) / 0.3); + color: var(--fallback-bc,oklch(var(--bc)/0.3)); } .menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ @@ -1157,8 +1265,12 @@ html{ --tw-scale-x: .9; --tw-scale-y: .9; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); padding: 1.5rem; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; @@ -1166,10 +1278,6 @@ html{ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0, 0, 0.2, 1); transition-duration: 200ms; - border-top-left-radius: var(--rounded-box, 1rem); - border-top-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); - border-bottom-right-radius: var(--rounded-box, 1rem); box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; overflow-y: auto; overscroll-behavior: contain; @@ -1194,32 +1302,118 @@ html{ overflow: hidden; } +.radio{ + flex-shrink: 0; + --chkbg: var(--bc); + height: 1.5rem; + width: 1.5rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 9999px; + border-width: 1px; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; +} + +.tabs{ + display: grid; + align-items: flex-end; +} + +.tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(.tab-active), + .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(.tab-active){ + border-bottom-color: transparent; +} + +.tab{ + position: relative; + grid-row-start: 1; + display: inline-flex; + height: 2rem; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; + --tw-text-opacity: 0.5; + --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); + --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); + --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); + color: var(--tab-color); + -webkit-padding-start: var(--tab-padding, 1rem); + padding-inline-start: var(--tab-padding, 1rem); + -webkit-padding-end: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); +} + +.tab:is(input[type="radio"]):after{ + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.tab:not(input):empty{ + cursor: default; + grid-column-start: span 9999; +} + +.tab-content{ + grid-column: 1 / -1; + grid-column-end: span 9999; + grid-row-start: 2; + margin-top: calc(var(--tab-border) * -1); + display: none; + border-color: transparent; + border-width: var(--tab-border, 0); +} + +:checked + .tab-content:nth-child(2), + .tab-active + .tab-content:nth-child(2){ + border-start-start-radius: 0px; +} + +input.tab:checked + .tab-content, +.tab-active + .tab-content{ + display: block; +} + .avatar-group :where(.avatar){ overflow: hidden; border-radius: 9999px; border-width: 4px; --tw-border-opacity: 1; - border-color: hsl(var(--b1) / var(--tw-border-opacity)); + border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); } .badge-neutral{ --tw-border-opacity: 1; - border-color: hsl(var(--n) / var(--tw-border-opacity)); + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-text-opacity: 1; - color: hsl(var(--nc) / var(--tw-text-opacity)); + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); } .badge-outline.badge-neutral{ --tw-text-opacity: 1; - color: hsl(var(--n) / var(--tw-text-opacity)); + color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); } .btm-nav > *:where(.active){ border-top-width: 2px; --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); } .btm-nav > * .label{ @@ -1233,27 +1427,33 @@ html{ transform: scale(var(--btn-focus-scale, 0.97)); } +@supports not (color: oklch(0 0 0)){ + .btn{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + + .btn-neutral{ + --btn-color: var(--fallback-n); + } +} + .btn:focus-visible{ outline-style: solid; outline-width: 2px; outline-offset: 2px; } -.btn-neutral{ - --tw-border-opacity: 1; - border-color: hsl(var(--n) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--nc) / var(--tw-text-opacity)); - outline-color: hsl(var(--n) / 1); +@supports (color: oklch(0 0 0)){ + .btn-neutral{ + --btn-color: var(--n); + } } -.btn-neutral.btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--nf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--nf) / var(--tw-bg-opacity)); +.btn-neutral{ + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + outline-color: var(--fallback-n,oklch(var(--n)/1)); } .btn.glass{ @@ -1280,49 +1480,32 @@ html{ } .btn-ghost.btn-active{ - --tw-border-opacity: 0; - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.2; + border-color: transparent; + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } .btn.btn-disabled, .btn[disabled], .btn:disabled{ --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-bg-opacity: 0.2; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); --tw-text-opacity: 0.2; } -.btn-group > input[type="radio"]:checked.btn, - .btn-group > .btn-active{ - --tw-border-opacity: 1; - border-color: hsl(var(--p) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--p) / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: hsl(var(--pc) / var(--tw-text-opacity)); -} - -.btn-group > input[type="radio"]:checked.btn:focus-visible, .btn-group > .btn-active:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-color: hsl(var(--p) / 1); -} - .btn:is(input[type="checkbox"]:checked), .btn:is(input[type="radio"]:checked){ --tw-border-opacity: 1; - border-color: hsl(var(--p) / var(--tw-border-opacity)); + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--p) / var(--tw-bg-opacity)); + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); --tw-text-opacity: 1; - color: hsl(var(--pc) / var(--tw-text-opacity)); + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); } .btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ - outline-color: hsl(var(--p) / 1); + outline-color: var(--fallback-p,oklch(var(--p)/1)); } @keyframes button-pop{ @@ -1372,7 +1555,7 @@ details.collapse summary::-webkit-details-marker{ outline-style: solid; outline-width: 2px; outline-offset: 2px; - outline-color: hsl(var(--bc) / 1); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); } .collapse:has(.collapse-title:focus-visible), @@ -1381,7 +1564,7 @@ details.collapse summary::-webkit-details-marker{ outline-style: solid; outline-width: 2px; outline-offset: 2px; - outline-color: hsl(var(--bc) / 1); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); } .collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], @@ -1404,7 +1587,8 @@ details.collapse summary::-webkit-details-marker{ :where(.collapse > input[type="radio"]){ width: 100%; padding: 1rem; - padding-right: 3rem; + -webkit-padding-end: 3rem; + padding-inline-end: 3rem; min-height: 3.75rem; transition: background-color 0.2s ease-out; } @@ -1429,16 +1613,6 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -[dir="rtl"] .collapse[open].collapse-arrow > .collapse-title:after, -[dir="rtl"] .collapse-open.collapse-arrow > .collapse-title:after, -[dir="rtl"] .collapse-arrow:focus:not(.collapse-close) .collapse-title:after, -[dir="rtl"] - .collapse-arrow:not(.collapse-close) - input[type="checkbox"]:checked - ~ .collapse-title:after{ - --tw-rotate: 135deg; -} - .collapse[open].collapse-plus > .collapse-title:after, .collapse-open.collapse-plus > .collapse-title:after, .collapse-plus:focus:not(.collapse-close) > .collapse-title:after, @@ -1447,16 +1621,6 @@ details.collapse summary::-webkit-details-marker{ content: "−"; } -.divider:before{ - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; -} - -.divider:after{ - background-color: hsl(var(--bc) / var(--tw-bg-opacity)); - --tw-bg-opacity: 0.1; -} - .divider:not(:empty){ gap: 1rem; } @@ -1477,51 +1641,59 @@ details.collapse summary::-webkit-details-marker{ outline-style: solid; outline-width: 2px; outline-offset: 2px; - outline-color: hsl(var(--bc) / 0.2); + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } .file-input-disabled, .file-input[disabled]{ cursor: not-allowed; --tw-border-opacity: 1; - border-color: hsl(var(--b2) / var(--tw-border-opacity)); + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); --tw-text-opacity: 0.2; } .file-input-disabled::-moz-placeholder, .file-input[disabled]::-moz-placeholder{ - color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); --tw-placeholder-opacity: 0.2; } .file-input-disabled::placeholder, .file-input[disabled]::placeholder{ - color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); --tw-placeholder-opacity: 0.2; } .file-input-disabled::file-selector-button, .file-input[disabled]::file-selector-button{ --tw-border-opacity: 0; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-bg-opacity: 0.2; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); --tw-text-opacity: 0.2; } +.input input:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .input[list]::-webkit-calendar-picker-indicator{ line-height: 1em; } .input-bordered{ - --tw-border-opacity: 0.2; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } -.input:focus{ +.input:focus, + .input:focus-within{ + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); outline-style: solid; outline-width: 2px; outline-offset: 2px; - outline-color: hsl(var(--bc) / 0.2); + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } .input-disabled, @@ -1529,28 +1701,29 @@ details.collapse summary::-webkit-details-marker{ .input[disabled]{ cursor: not-allowed; --tw-border-opacity: 1; - border-color: hsl(var(--b2) / var(--tw-border-opacity)); + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); - --tw-text-opacity: 0.2; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); } .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder{ - color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); --tw-placeholder-opacity: 0.2; } .input-disabled::placeholder, .input:disabled::placeholder, .input[disabled]::placeholder{ - color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); --tw-placeholder-opacity: 0.2; } .join > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-left: -1px; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .link:focus{ @@ -1593,35 +1766,35 @@ details.collapse summary::-webkit-details-marker{ } :where(.menu li:empty){ - background-color: hsl(var(--bc) / 0.1); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); margin: 0.5rem 1rem; height: 1px; } .menu :where(li ul):before{ position: absolute; - left: 0px; - top: 0.75rem; bottom: 0.75rem; + inset-inline-start: 0px; + top: 0.75rem; width: 1px; - background-color: hsl(var(--bc) / 0.1); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); content: ""; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ + border-radius: var(--rounded-btn, 0.5rem); padding-left: 1rem; padding-right: 1rem; padding-top: 0.5rem; padding-bottom: 0.5rem; - text-align: left; + text-align: start; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0, 0, 0.2, 1); transition-duration: 200ms; - border-radius: var(--rounded-btn, 0.5rem); text-wrap: balance; } @@ -1632,9 +1805,9 @@ details.collapse summary::-webkit-details-marker{ :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ cursor: pointer; - background-color: hsl(var(--bc) / 0.1); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); outline: 2px solid transparent; outline-offset: 2px; } @@ -1643,9 +1816,9 @@ details.collapse summary::-webkit-details-marker{ .menu li > *:not(ul):not(.menu-title):not(details).active, .menu li > details > summary:active{ --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); --tw-text-opacity: 1; - color: hsl(var(--nc) / var(--tw-text-opacity)); + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); } .menu :where(li > details > summary)::-webkit-details-marker{ @@ -1692,15 +1865,16 @@ details.collapse summary::-webkit-details-marker{ text-overflow: ellipsis; white-space: nowrap; --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); padding-left: 2rem; + direction: ltr; } .mockup-browser .mockup-browser-toolbar .input:before{ content: ""; position: absolute; - top: 50%; left: 0.5rem; + top: 50%; aspect-ratio: 1 / 1; height: 0.75rem; --tw-translate-y: -50%; @@ -1714,8 +1888,8 @@ details.collapse summary::-webkit-details-marker{ .mockup-browser .mockup-browser-toolbar .input:after{ content: ""; position: absolute; - top: 50%; left: 1.25rem; + top: 50%; height: 0.5rem; --tw-translate-y: 25%; --tw-rotate: -45deg; @@ -1760,20 +1934,46 @@ details.collapse summary::-webkit-details-marker{ } } +.radio:focus{ + box-shadow: none; +} + +.radio:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.radio:checked, + .radio[aria-checked="true"]{ + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-image: none; + animation: radiomark var(--animation-input, 0.2s) ease-out; + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; +} + +.radio:disabled{ + cursor: not-allowed; + opacity: 0.2; +} + @keyframes radiomark{ 0%{ - box-shadow: 0 0 0 12px hsl(var(--b1)) inset, - 0 0 0 12px hsl(var(--b1)) inset; + box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset; } 50%{ - box-shadow: 0 0 0 3px hsl(var(--b1)) inset, - 0 0 0 3px hsl(var(--b1)) inset; + box-shadow: 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset; } 100%{ - box-shadow: 0 0 0 4px hsl(var(--b1)) inset, - 0 0 0 4px hsl(var(--b1)) inset; + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; } } @@ -1791,18 +1991,169 @@ details.collapse summary::-webkit-details-marker{ } } +@keyframes skeleton{ + from{ + background-position: 150%; + } + + to{ + background-position: -50%; + } +} + +.tabs-lifted .tab:focus-visible{ + border-end-end-radius: 0; + border-end-start-radius: 0; +} + +.tab.tab-active:not(.tab-disabled):not([disabled]), + .tab:is(input:checked){ + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 1; + --tw-text-opacity: 1; +} + +.tab:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + +.tab:focus-visible{ + outline: 2px solid currentColor; + outline-offset: -5px; +} + +.tab-disabled, + .tab[disabled]{ + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.tabs-bordered .tab{ + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; + border-style: solid; + border-bottom-width: calc(var(--tab-border, 1px) + 1px); +} + +.tabs-lifted .tab{ + border: var(--tab-border, 1px) solid transparent; + border-width: 0 0 var(--tab-border, 1px) 0; + border-start-start-radius: var(--tab-radius, 0.5rem); + border-start-end-radius: var(--tab-radius, 0.5rem); + border-bottom-color: var(--tab-border-color); + -webkit-padding-start: var(--tab-padding, 1rem); + padding-inline-start: var(--tab-padding, 1rem); + -webkit-padding-end: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); + padding-top: var(--tab-border, 1px); +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]), + .tabs-lifted .tab:is(input:checked){ + background-color: var(--tab-bg); + border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); + border-inline-start-color: var(--tab-border-color); + border-inline-end-color: var(--tab-border-color); + border-top-color: var(--tab-border-color); + -webkit-padding-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + -webkit-padding-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-bottom: var(--tab-border, 1px); + padding-top: 0; +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked):before{ + z-index: 1; + content: ""; + display: block; + position: absolute; + width: calc(100% + var(--tab-radius, 0.5rem) * 2); + height: var(--tab-radius, 0.5rem); + bottom: 0; + background-size: var(--tab-radius, 0.5rem); + background-position: top left, + top right; + background-repeat: no-repeat; + --tab-grad: calc(69% - var(--tab-border, 1px)); + --radius-start: radial-gradient( + circle at top left, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + --radius-end: radial-gradient( + circle at top right, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + background-image: var(--radius-start), var(--radius-end); +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted .tab:is(input:checked):first-child:before{ + background-image: var(--radius-end); + background-position: top right; +} + +[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):first-child:before{ + background-image: var(--radius-start); + background-position: top left; +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted .tab:is(input:checked):last-child:before{ + background-image: var(--radius-start); + background-position: top left; +} + +[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):last-child:before{ + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-lifted + .tab-active:not(.tab-disabled):not([disabled]) + + .tabs-lifted + .tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before{ + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-boxed{ + border-radius: var(--rounded-btn, 0.5rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding: 0.25rem; +} + +.tabs-boxed .tab{ + border-radius: var(--rounded-btn, 0.5rem); +} + +.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), + .tabs-boxed :is(input:checked){ + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } .table-zebra tr.active, .table-zebra tr.active:nth-child(even), .table-zebra-zebra tbody tr:nth-child(even){ --tw-bg-opacity: 1; - background-color: hsl(var(--b3) / var(--tw-bg-opacity)); + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } @keyframes toast-pop{ @@ -1817,10 +2168,6 @@ details.collapse summary::-webkit-details-marker{ } } -.rounded-box{ - border-radius: var(--rounded-box, 1rem); -} - .no-animation{ --btn-focus-scale: 1; --animation-btn: 0; @@ -1845,17 +2192,17 @@ details.collapse summary::-webkit-details-marker{ .btn-xs{ height: 1.5rem; + min-height: 1.5rem; padding-left: 0.5rem; padding-right: 0.5rem; - min-height: 1.5rem; font-size: 0.75rem; } .btn-sm{ height: 2rem; + min-height: 2rem; padding-left: 0.75rem; padding-right: 0.75rem; - min-height: 2rem; font-size: 0.875rem; } @@ -1905,18 +2252,18 @@ details.collapse summary::-webkit-details-marker{ .join.join-vertical .join-item:first-child:not(:last-child), .join.join-vertical *:first-child:not(:last-child) .join-item{ - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-top-left-radius: inherit; - border-top-right-radius: inherit; + border-end-start-radius: 0; + border-end-end-radius: 0; + border-start-start-radius: inherit; + border-start-end-radius: inherit; } .join.join-vertical .join-item:last-child:not(:first-child), .join.join-vertical *:last-child:not(:first-child) .join-item{ - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; + border-start-start-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-end-end-radius: inherit; } .join.join-horizontal{ @@ -1925,24 +2272,54 @@ details.collapse summary::-webkit-details-marker{ .join.join-horizontal .join-item:first-child:not(:last-child), .join.join-horizontal *:first-child:not(:last-child) .join-item{ - border-bottom-right-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: inherit; - border-top-left-radius: inherit; + border-end-end-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-start-start-radius: inherit; } .join.join-horizontal .join-item:last-child:not(:first-child), .join.join-horizontal *:last-child:not(:first-child) .join-item{ - border-bottom-left-radius: 0; - border-top-left-radius: 0; - border-bottom-right-radius: inherit; - border-top-right-radius: inherit; + border-end-start-radius: 0; + border-start-start-radius: 0; + border-end-end-radius: inherit; + border-start-end-radius: inherit; } .modal-bottom{ place-items: end; } +.tabs-md :where(.tab){ + height: 2rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; +} + +.tabs-lg :where(.tab){ + height: 3rem; + font-size: 1.125rem; + line-height: 1.75rem; + line-height: 2; + --tab-padding: 1.25rem; +} + +.tabs-sm :where(.tab){ + height: 1.5rem; + font-size: 0.875rem; + line-height: .75rem; + --tab-padding: 0.75rem; +} + +.tabs-xs :where(.tab){ + height: 1.25rem; + font-size: 0.75rem; + line-height: .75rem; + --tab-padding: 0.5rem; +} + .avatar.online:before{ content: ""; position: absolute; @@ -1950,10 +2327,10 @@ details.collapse summary::-webkit-details-marker{ display: block; border-radius: 9999px; --tw-bg-opacity: 1; - background-color: hsl(var(--su) / var(--tw-bg-opacity)); + background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); outline-style: solid; outline-width: 2px; - outline-color: hsl(var(--b1) / 1); + outline-color: var(--fallback-b1,oklch(var(--b1)/1)); width: 15%; height: 15%; top: 7%; @@ -1967,78 +2344,16 @@ details.collapse summary::-webkit-details-marker{ display: block; border-radius: 9999px; --tw-bg-opacity: 1; - background-color: hsl(var(--b3) / var(--tw-bg-opacity)); + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); outline-style: solid; outline-width: 2px; - outline-color: hsl(var(--b1) / 1); + outline-color: var(--fallback-b1,oklch(var(--b1)/1)); width: 15%; height: 15%; top: 7%; right: 7%; } -.btn-group .btn:not(:first-child):not(:last-child){ - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group .btn:first-child:not(:last-child){ - margin-top: -0px; - margin-left: -1px; - border-top-left-radius: var(--rounded-btn, 0.5rem); - border-top-right-radius: 0; - border-bottom-left-radius: var(--rounded-btn, 0.5rem); - border-bottom-right-radius: 0; -} - -.btn-group .btn:last-child:not(:first-child){ - border-top-left-radius: 0; - border-top-right-radius: var(--rounded-btn, 0.5rem); - border-bottom-left-radius: 0; - border-bottom-right-radius: var(--rounded-btn, 0.5rem); -} - -.btn-group-horizontal .btn:not(:first-child):not(:last-child){ - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group-horizontal .btn:first-child:not(:last-child){ - margin-top: -0px; - margin-left: -1px; - border-top-left-radius: var(--rounded-btn, 0.5rem); - border-top-right-radius: 0; - border-bottom-left-radius: var(--rounded-btn, 0.5rem); - border-bottom-right-radius: 0; -} - -.btn-group-horizontal .btn:last-child:not(:first-child){ - border-top-left-radius: 0; - border-top-right-radius: var(--rounded-btn, 0.5rem); - border-bottom-left-radius: 0; - border-bottom-right-radius: var(--rounded-btn, 0.5rem); -} - -.btn-group-vertical .btn:first-child:not(:last-child){ - margin-top: -1px; - margin-left: -0px; - border-top-left-radius: var(--rounded-btn, 0.5rem); - border-top-right-radius: var(--rounded-btn, 0.5rem); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group-vertical .btn:last-child:not(:first-child){ - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: var(--rounded-btn, 0.5rem); - border-bottom-right-radius: var(--rounded-btn, 0.5rem); -} - .join.join-vertical > :where(*:not(:first-child)){ margin-left: 0px; margin-right: 0px; @@ -2048,7 +2363,8 @@ details.collapse summary::-webkit-details-marker{ .join.join-horizontal > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-left: -1px; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .modal-top :where(.modal-box){ @@ -2058,10 +2374,10 @@ details.collapse summary::-webkit-details-marker{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); border-top-left-radius: 0px; border-top-right-radius: 0px; - border-bottom-left-radius: var(--rounded-box, 1rem); - border-bottom-right-radius: var(--rounded-box, 1rem); } .modal-middle :where(.modal-box){ @@ -2073,8 +2389,8 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); border-top-left-radius: var(--rounded-box, 1rem); border-top-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); } .modal-bottom :where(.modal-box){ @@ -2084,10 +2400,10 @@ details.collapse summary::-webkit-details-marker{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; border-top-left-radius: var(--rounded-box, 1rem); border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; } .pointer-events-none{ @@ -2134,14 +2450,14 @@ details.collapse summary::-webkit-details-marker{ left: 50%; } -.left-12{ - left: 3rem; -} - .left-2{ left: 0.5rem; } +.left-28{ + left: 7rem; +} + .left-4{ left: 1rem; } @@ -2174,18 +2490,6 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } -.left-24{ - left: 6rem; -} - -.left-32{ - left: 8rem; -} - -.left-28{ - left: 7rem; -} - .z-\[1000\]{ z-index: 1000; } @@ -2272,6 +2576,10 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } +.h-\[400px\]{ + height: 400px; +} + .min-h-\[600px\]{ min-height: 600px; } @@ -2309,6 +2617,18 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-\[32rem\]{ + width: 32rem; +} + +.w-32{ + width: 8rem; +} + +.w-\[400px\]{ + width: 400px; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2387,6 +2707,10 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.25rem; } +.rounded-box{ + border-radius: var(--rounded-box, 1rem); +} + .rounded-full{ border-radius: 9999px; } @@ -2447,7 +2771,7 @@ details.collapse summary::-webkit-details-marker{ .bg-base-200{ --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } .bg-black{ @@ -2467,7 +2791,7 @@ details.collapse summary::-webkit-details-marker{ .bg-neutral{ --tw-bg-opacity: 1; - background-color: hsl(var(--n) / var(--tw-bg-opacity)); + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); } .bg-orange-100{ @@ -2536,6 +2860,10 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.p-10{ + padding: 2.5rem; +} + .p-2{ padding: 0.5rem; } @@ -2620,7 +2948,7 @@ details.collapse summary::-webkit-details-marker{ .text-base-content{ --tw-text-opacity: 1; - color: hsl(var(--bc) / var(--tw-text-opacity)); + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } .text-black{ @@ -2824,6 +3152,27 @@ img[src] { } @media (min-width: 640px){ + .sm\:btn-md{ + height: 3rem; + min-height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 0.875rem; + } + + .btn-square:where(.sm\:btn-md){ + height: 3rem; + width: 3rem; + padding: 0px; + } + + .btn-circle:where(.sm\:btn-md){ + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; + } + .sm\:modal-middle{ place-items: center; } @@ -2837,17 +3186,22 @@ img[src] { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); border-top-left-radius: var(--rounded-box, 1rem); border-top-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); } } -.hover\:bg-neutral-focus:hover{ - --tw-bg-opacity: 1; - background-color: hsl(var(--nf) / var(--tw-bg-opacity)); -} - .hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); } + +@media (min-width: 640px){ + .sm\:flex{ + display: flex; + } + + .sm\:w-10{ + width: 2.5rem; + } +} From 17ff4787beca0a41caa990acb8478a08a9d84495 Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 15 Nov 2023 17:40:13 +0800 Subject: [PATCH 050/127] fix: load image node type --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index e9b12ec..6f73540 100644 --- a/js/index.js +++ b/js/index.js @@ -245,7 +245,7 @@ function getInputWidgetValue(node, inputIndex, widgetName) { console.log(targetLink, nodea); console.log(nodea.getInputNode(0, true)); - if (nodea.type !== "IMAGE") { + if (nodea.type !== "LoadImage") { return; } From e1b96b510cc1bfa567c22edb4b1e43551311d23e Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 15 Nov 2023 17:44:18 +0800 Subject: [PATCH 051/127] fix: image url --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 6f73540..34eef3d 100644 --- a/js/index.js +++ b/js/index.js @@ -311,7 +311,7 @@ function showMyImageEditor(node) { } showImageEditor.val = true; const isGeneratedImage = connectedImageFileName.startsWith("avatar"); - const subfolder = isGeneratedImage || split.length == 0 ? "" : split[0]; + const subfolder = isGeneratedImage || split.length === 1 ? "" : split[0]; imageUrl.val = api.apiURL( `/view?filename=${encodeURIComponent(connectedImageFileName)}&type=${ isGeneratedImage ? "output" : "input" From c401a79b93495c4641795e3049b752c0b94fdf02 Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 18:56:07 +0800 Subject: [PATCH 052/127] feat: drag and drop file --- js/AvatarPreview.js | 161 +++++++++++++++++++++++++++++--------------- js/tw-styles.css | 59 ++++++++++++++++ 2 files changed, 166 insertions(+), 54 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index e886c72..0dabb21 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -44,28 +44,57 @@ async function uploadImage() { const jsonWorkflowLoading = van.state(true); async function prepareImageFromUrlRedirect(stage) { - await new Promise(resolve => setTimeout(resolve, 2000)); - const queue_id = new URLSearchParams(window.location.search).get('queue-id') - if ( - queue_id && queue_id != "" - ) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + const queue_id = new URLSearchParams(window.location.search).get("queue-id"); + if (queue_id && queue_id != "") { console.log(queue_id); - stage.val = 1 + stage.val = 1; const graph = app.graph; const node = graph.findNodesByType("LoadImage"); - const imageName = queue_id + ".png" + const imageName = queue_id + ".png"; console.log(node[0]); - node[0].widgets_values[0] = imageName - node[0].widgets[0].value = imageName - node[0].widgets[0]._value = imageName - graph.change() + node[0].widgets_values[0] = imageName; + node[0].widgets[0].value = imageName; + node[0].widgets[0]._value = imageName; + graph.change(); previewImg.val = api.apiURL( `/view?filename=${encodeURIComponent( - imageName - )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}` + imageName, + )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}`, ); console.log(previewImg); } + const dragndrop = document.getElementById("dnd"); + dragndrop.addEventListener("dragenter", (evt) => { + evt.preventDefault(); + dragndrop.className = + "w-full border-2 border-purple-500 text-purple-500 border-dashed py-4 rounded-lg flex justify-center"; + }); + dragndrop.addEventListener("dragleave", (evt) => { + evt.preventDefault(); + dragndrop.className = + "w-full border-2 border-black border-dashed py-4 rounded-lg flex justify-center"; + }); + dragndrop.addEventListener("dragover", (evt) => { + evt.preventDefault(); + }); + dragndrop.addEventListener("drop", async (evt) => { + evt.preventDefault(); + dragndrop.className = + "w-full border-2 border-black border-dashed py-4 rounded-lg flex justify-center"; + if (evt.dataTransfer.files.length > 1) return; + if ( + evt.dataTransfer.files[0].type != "image/jpeg" && + evt.dataTransfer.files[0].type != "image/png" && + evt.dataTransfer.files[0].type != "image/webp" + ) + return; + stage.val = 1; + previewImg.val = URL.createObjectURL(evt.dataTransfer.files[0]); + if (Object.entries(evt.dataTransfer.files).length) { + await uploadFile(evt.dataTransfer.files[0], true); + } + }); } export function AvatarPreview() { @@ -82,6 +111,13 @@ export function AvatarPreview() { loading.val = true; }); + document.addEventListener("dragenter", (evt) => { + console.log("asdasd"); + }); + window.addEventListener("drop", (evt) => { + console.log("zxczxc"); + }); + api.addEventListener("executed", (evt) => { loading.val = false; }); @@ -90,7 +126,7 @@ export function AvatarPreview() { const stage = van.state(0); // 0: upload image, 1: edit segment, 2: generate // This will wait 2 seconds until the everything is loaded - prepareImageFromUrlRedirect(stage) + prepareImageFromUrlRedirect(stage); const renderSteps = () => { return div( @@ -154,46 +190,62 @@ export function AvatarPreview() { { class: () => "flex flex-col justify-center items-center gap-4", }, - button( - { - class: () => - "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", - onclick: async () => { - // previewImg.val = await uploadImage(); - // stage.val = 1; - var input = document.createElement("input"); - input.type = "file"; + div( + { class: () => "w-full flex mt-2" }, + button( + { + class: () => + "h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + onclick: async () => { + // previewImg.val = await uploadImage(); + // stage.val = 1; + var input = document.createElement("input"); + input.type = "file"; - document.body.appendChild(input); + document.body.appendChild(input); - // when the input content changes, do something - input.onchange = async function (e) { - stage.val = 1; - if (Object.entries(e.target.files).length) { - await uploadFile(e.target.files[0], true); - } - previewImg.val = URL.createObjectURL(e.target.files[0]); - // upload files - document.body.removeChild(input); - }; + // when the input content changes, do something + input.onchange = async function (e) { + stage.val = 1; + if (Object.entries(e.target.files).length) { + await uploadFile(e.target.files[0], true); + } + previewImg.val = URL.createObjectURL(e.target.files[0]); + // upload files + document.body.removeChild(input); + }; - // Trigger file browser - input.click(); + // Trigger file browser + input.click(); + }, }, - }, - div({ class: "badge badge-neutral" }, "1"), - div("Upload your image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", - }), - () => - previewImgLoading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "", + div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }), + () => + previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-md", + }) + : "", + ), + previewImg.val == "" + ? div({ class: () => "divider divider-horizontal !gap-0" }, "OR") + : "", + previewImg.val == "" + ? div( + { + id: "dnd", + class: () => + "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", + }, + "Drag and drop the image here", + ) + : "", ), () => previewImg.val != "" @@ -203,6 +255,7 @@ export function AvatarPreview() { src: previewImg, }) : "", + button( { class: () => @@ -470,7 +523,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "mdi:restart", "data-inline": "false", - }) + }), ); }; @@ -500,8 +553,8 @@ export function AvatarPreview() { "data-inline": "false", }), span({ class: "sm:flex hidden" }, () => - jsonWorkflowLoading.val ? "Loading" : "Change workflow" - ) + jsonWorkflowLoading.val ? "Loading" : "Change workflow", + ), ); }; @@ -539,7 +592,7 @@ export function AvatarPreview() { }, renderCloseButton(), renderRestartButton(), - renderChangeWorkflowButton() + renderChangeWorkflowButton(), ), renderTwitter(), () => { diff --git a/js/tw-styles.css b/js/tw-styles.css index 37a597f..0c3d651 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2246,6 +2246,20 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.divider-horizontal{ + flex-direction: column; +} + +.divider-horizontal:before{ + height: 100%; + width: 0.125rem; +} + +.divider-horizontal:after{ + height: 100%; + width: 0.125rem; +} + .join.join-vertical{ flex-direction: column; } @@ -2354,6 +2368,15 @@ details.collapse summary::-webkit-details-marker{ right: 7%; } +.divider-horizontal{ + margin-left: 1rem; + margin-right: 1rem; + margin-top: 0px; + margin-bottom: 0px; + height: auto; + width: 1rem; +} + .join.join-vertical > :where(*:not(:first-child)){ margin-left: 0px; margin-right: 0px; @@ -2580,6 +2603,10 @@ details.collapse summary::-webkit-details-marker{ height: 400px; } +.h-24{ + height: 6rem; +} + .min-h-\[600px\]{ min-height: 600px; } @@ -2687,6 +2714,10 @@ details.collapse summary::-webkit-details-marker{ gap: 1rem; } +.\!gap-0{ + gap: 0px !important; +} + .place-self-end{ place-self: end; } @@ -2751,10 +2782,18 @@ details.collapse summary::-webkit-details-marker{ border-width: 1px; } +.border-2{ + border-width: 2px; +} + .border-t-4{ border-top-width: 4px; } +.border-dashed{ + border-style: dashed; +} + .border-none{ border-style: none; } @@ -2764,6 +2803,16 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(249 115 22 / var(--tw-border-opacity)); } +.border-black{ + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + +.border-purple-500{ + --tw-border-opacity: 1; + border-color: rgb(168 85 247 / var(--tw-border-opacity)); +} + .bg-\[\#f6f6f6\]{ --tw-bg-opacity: 1; background-color: rgb(246 246 246 / var(--tw-bg-opacity)); @@ -2901,6 +2950,11 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } +.py-4{ + padding-top: 1rem; + padding-bottom: 1rem; +} + .text-start{ text-align: start; } @@ -2980,6 +3034,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } +.text-purple-500{ + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + .opacity-80{ opacity: 0.8; } From a39070c5454cd9cd21e6c0a4cc61020d12ab8c94 Mon Sep 17 00:00:00 2001 From: San Date: Wed, 15 Nov 2023 18:59:46 +0800 Subject: [PATCH 053/127] fix: drag and drop ui issue --- js/AvatarPreview.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 0dabb21..432f190 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -68,12 +68,12 @@ async function prepareImageFromUrlRedirect(stage) { dragndrop.addEventListener("dragenter", (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-purple-500 text-purple-500 border-dashed py-4 rounded-lg flex justify-center"; + "w-full border-2 border-purple-500 text-purple-500 border-dashed rounded-lg flex justify-center items-center"; }); dragndrop.addEventListener("dragleave", (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-black border-dashed py-4 rounded-lg flex justify-center"; + "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; }); dragndrop.addEventListener("dragover", (evt) => { evt.preventDefault(); @@ -81,7 +81,7 @@ async function prepareImageFromUrlRedirect(stage) { dragndrop.addEventListener("drop", async (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-black border-dashed py-4 rounded-lg flex justify-center"; + "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; if (evt.dataTransfer.files.length > 1) return; if ( evt.dataTransfer.files[0].type != "image/jpeg" && @@ -111,13 +111,6 @@ export function AvatarPreview() { loading.val = true; }); - document.addEventListener("dragenter", (evt) => { - console.log("asdasd"); - }); - window.addEventListener("drop", (evt) => { - console.log("zxczxc"); - }); - api.addEventListener("executed", (evt) => { loading.val = false; }); @@ -195,7 +188,7 @@ export function AvatarPreview() { button( { class: () => - "h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + `${previewImg.val != "" && 'w-full'} h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, onclick: async () => { // previewImg.val = await uploadImage(); // stage.val = 1; From b3eb1acfacf2f2f59163dc8db9ec5863cbc78efd Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 15 Nov 2023 19:22:55 +0800 Subject: [PATCH 054/127] feat: lora workflow --- js/AvatarPreview.js | 236 +++++++++++++++++++++++--------------------- js/onnx.js | 8 +- js/tw-styles.css | 186 ++++++++++++++++++++++++---------- 3 files changed, 263 insertions(+), 167 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 432f190..4882843 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -6,11 +6,14 @@ import { showEditor, previewImg, previewImgLoading, + alertDialog, } from "./state.js"; -const { button, iframe, div, img, input, label, span } = van.tags; +const { button, iframe, div, img, input, label, span, textarea } = van.tags; import { app } from "./app.js"; import { uploadPreview } from "./index.js"; import { api } from "./api.js"; +import { uploadSegments } from "./LayerEditor.js"; +import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; async function loadJSONWorkflow() { @@ -59,8 +62,8 @@ async function prepareImageFromUrlRedirect(stage) { graph.change(); previewImg.val = api.apiURL( `/view?filename=${encodeURIComponent( - imageName, - )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}`, + imageName + )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}` ); console.log(previewImg); } @@ -131,14 +134,14 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1", + "Avatech v1" ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone", + "Get your DALLE3 AI Personal Clone" ), // input({ // type: "file", @@ -188,7 +191,9 @@ export function AvatarPreview() { button( { class: () => - `${previewImg.val != "" && 'w-full'} h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, + `${ + previewImg.val != "" && "w-full" + } h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, onclick: async () => { // previewImg.val = await uploadImage(); // stage.val = 1; @@ -224,10 +229,13 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md", }) - : "", + : "" ), previewImg.val == "" - ? div({ class: () => "divider divider-horizontal !gap-0" }, "OR") + ? div( + { class: () => "divider divider-horizontal !gap-0" }, + "OR" + ) : "", previewImg.val == "" ? div( @@ -236,9 +244,9 @@ export function AvatarPreview() { class: () => "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", }, - "Drag and drop the image here", + "Drag and drop the image here" ) - : "", + : "" ), () => previewImg.val != "" @@ -271,7 +279,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit segment", + "Edit segment" ), button( { @@ -292,84 +300,110 @@ export function AvatarPreview() { () => loading.val ? span({ - class: "loading loading-spinner loading-lg", + class: "loading loading-spinner loading-md", }) - : "Generate", - ), - ), + : "Make it alive!" + ) + ) ), input({ type: "radio", name: "my_tabs_1", - class: () => "tab", + class: "tab", ariaLabel: "Generate avatar", }), div( - { class: () => "tab-content text-black w-full" }, + { class: "tab-content text-black w-full" }, div( - { - class: () => "flex flex-col justify-center items-center gap-4", - }, + { class: "flex flex-col justify-center items-center gap-4" }, + div( + { + class: + "w-full mt-2 flex flex-col rounded-md left-0 top-0 z-[200] flex", + }, + textarea({ + class: + "textarea textarea-bordered border-gray-300 border-b-0 focus:outline-none resize-none rounded-t-md rounded-b-none text-md h-36", + placeholder: "Enter your prompt", + defaultValue: + "1girl, looking at viewer, open mouth, simple background, white background, smile", + id: "positivePromptProxy", + }), + div( + { + class: + "flex flex-row gap-2 border border-gray-300 rounded-b-md text-md items-center", + }, + span({ class: "ml-4" }, "Seed"), + div({ class: "divider divider-horizontal m-0" }), + input({ + type: "text", + class: "input border-none focus:outline-none w-full p-0", + placeholder: "Seed", + defaultValue: "1234", + id: "seedProxy", + }) + ) + ), button( { - class: () => - "w-full mt-2 btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto ", + class: "btn w-full normal-case ", onclick: async () => { - // previewImg.val = await uploadImage(); - // stage.val = 1; - var input = document.createElement("input"); - input.type = "file"; + const positivePrompt = app.graph + .findNodesByType("CLIPTextEncode") + .find((x) => x.color == "#232"); + if (!positivePrompt) { + alertDialog.val = { + text: "Cannot find the CLIPTextEncode node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } + const kSampler = app.graph.findNodesByType("KSampler")[0]; + if (!kSampler) { + alertDialog.val = { + text: "Cannot find the KSampler node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } - document.body.appendChild(input); + positivePrompt.widgets[0].inputEl.value = + document.getElementById("positivePromptProxy").value; + kSampler.widgets[0].value = + document.getElementById("seedProxy").value; + kSampler.widgets[1].value = "fixed"; - // when the input content changes, do something - input.onchange = async function (e) { - stage.val = 1; - if (Object.entries(e.target.files).length) { - await uploadFile(e.target.files[0], true); - } - previewImg.val = URL.createObjectURL(e.target.files[0]); - // upload files - document.body.removeChild(input); - }; + loading.val = true; - // Trigger file browser - input.click(); + const sam = app.graph.findNodesByType("SAM MultiLayer")[0]; + if (!sam) { + alertDialog.val = { + text: "Cannot find the SAM node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } + const ckpt = sam.widgets[0].value; + const modelType = ckpt.match(/vit_[lbh]/)?.[0]; + await initModel(modelType); + await uploadSegments(); + + document.getElementById("queue-button").click(); }, }, div({ class: "badge badge-neutral" }, "1"), - div("Generate image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", - }), () => - previewImgLoading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "", + loading.val + ? span({ class: "loading loading-spinner loading-md" }) + : "Make It Alive!" ), - () => - previewImg.val != "" - ? img({ - class: () => - "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }) - : "", button( { - class: () => - "btn w-full normal-case " + - (stage.val < 1 ? "btn-disabled" : ""), + class: "btn w-full normal-case", onclick: () => { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - const nodes = graph.findNodesByType("SAM MultiLayer"); /** @type {any[]}*/ @@ -377,39 +411,15 @@ export function AvatarPreview() { console.log(nodes[0]); console.log(nodes[0].widgets); widgets.find((x) => x.type == "button").callback(); - stage.val = 2; }, }, div({ class: "badge badge-neutral" }, "2"), - "Auto segment", - ), - button( - { - class: () => - "btn w-full normal-case " + - (stage.val < 2 ? "btn-disabled" : ""), - onclick: async () => { - // const uploaded = await uploadSegments(); - // if (!uploaded) return; - - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - document.getElementById("queue-button").click(); - }, - }, - div({ class: "badge badge-neutral" }, "3"), - () => - loading.val - ? span({ - class: "loading loading-spinner loading-lg", - }) - : "Generate", - ), - ), - ), - ), - ), + "(Optional) Edit Segment" + ) + ) + ) + ) + ) ); }; @@ -420,7 +430,7 @@ export function AvatarPreview() { name: "avatech-viewer-iframe", allow: "cross-origin-isolated", class: () => - "w-full h-full min-w-[300px] min-h-[600px] z-[100] pointer-events-auto flex border-none overflow-hidden" + + "w-full h-full min-w-[300px] min-h-[400px] z-[100] pointer-events-auto flex border-none overflow-hidden" + (showPreview.val ? "" : "hidden"), // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", @@ -439,7 +449,7 @@ export function AvatarPreview() { class: () => "w-full flex justify-center font-bold italic text-gray-500", }, - span("We are launching OpenAI Assistant API integration soon!"), + span("We are launching OpenAI Assistant API integration soon!") ), div( { class: () => "w-[24rem] flex justify-center items-center" }, @@ -478,11 +488,11 @@ export function AvatarPreview() { () => shareLoading.val ? span({ - class: "loading loading-spinner loading-lg", + class: "loading loading-spinner loading-md", }) - : "Get Avatar Link", - ), - ), + : "Get Avatar Link" + ) + ) ); }; @@ -499,7 +509,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "ic:round-close", "data-inline": "false", - }), + }) ); }; @@ -516,7 +526,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "mdi:restart", "data-inline": "false", - }), + }) ); }; @@ -546,8 +556,8 @@ export function AvatarPreview() { "data-inline": "false", }), span({ class: "sm:flex hidden" }, () => - jsonWorkflowLoading.val ? "Loading" : "Change workflow", - ), + jsonWorkflowLoading.val ? "Loading" : "Change workflow" + ) ); }; @@ -558,7 +568,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn sm:w-10 w-32 text-black btn-ghost text-xs !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter", + "Twitter" ); }; @@ -585,7 +595,7 @@ export function AvatarPreview() { }, renderCloseButton(), renderRestartButton(), - renderChangeWorkflowButton(), + renderChangeWorkflowButton() ), renderTwitter(), () => { @@ -598,7 +608,7 @@ export function AvatarPreview() { }, renderIFrame(), renderSteps(), - renderShareLink(), + renderShareLink() ); } else { return div( @@ -611,12 +621,12 @@ export function AvatarPreview() { div( { class: () => "flex flex-col" }, renderIFrame(), - renderShareLink(), - ), + renderShareLink() + ) ); } - }, - ), + } + ) ); } @@ -636,8 +646,8 @@ function showImage(name) { } img.src = api.apiURL( `/view?filename=${encodeURIComponent( - name, - )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`, + name + )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}` ); node.setSizeForImage?.(); } diff --git a/js/onnx.js b/js/onnx.js index 6dae0ae..c416cfd 100644 --- a/js/onnx.js +++ b/js/onnx.js @@ -10,9 +10,11 @@ export let model = null; // Initialize the ONNX model export const initModel = async (modelType) => { try { - model = await ort.InferenceSession.create( - `${location.protocol}//${location.host}/sam_model?type=${modelType}` - ); + if (!model) { + model = await ort.InferenceSession.create( + `${location.protocol}//${location.host}/sam_model?type=${modelType}` + ); + } } catch (e) { console.log(e); } diff --git a/js/tw-styles.css b/js/tw-styles.css index 0c3d651..6e03c7a 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -730,12 +730,6 @@ html{ .tab:hover{ --tw-text-opacity: 1; } - - .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, - .tabs-boxed :is(input:checked):hover{ - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); - } } .btn{ @@ -1388,6 +1382,23 @@ input.tab:checked + .tab-content, display: block; } +.textarea{ + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + .avatar-group :where(.avatar){ overflow: hidden; border-radius: 9999px; @@ -1522,6 +1533,12 @@ input.tab:checked + .tab-content, } } +.card.bordered{ + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + @keyframes checkmark{ 0%{ background-position-y: 5px; @@ -2123,25 +2140,10 @@ details.collapse summary::-webkit-details-marker{ background-position: top right; } -.tabs-boxed{ - border-radius: var(--rounded-btn, 0.5rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - padding: 0.25rem; -} - .tabs-boxed .tab{ border-radius: var(--rounded-btn, 0.5rem); } -.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), - .tabs-boxed :is(input:checked){ - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2156,6 +2158,42 @@ details.collapse summary::-webkit-details-marker{ background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } +.textarea-bordered{ + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea:focus{ + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[disabled]{ + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + @keyframes toast-pop{ 0%{ transform: scale(0.9); @@ -2537,6 +2575,10 @@ details.collapse summary::-webkit-details-marker{ z-index: 99; } +.m-0{ + margin: 0px; +} + .my-0{ margin-top: 0px; margin-bottom: 0px; @@ -2566,6 +2608,10 @@ details.collapse summary::-webkit-details-marker{ margin-top: 2rem; } +.ml-4{ + margin-left: 1rem; +} + .block{ display: block; } @@ -2599,36 +2645,44 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } -.h-\[400px\]{ - height: 400px; -} - .h-24{ height: 6rem; } +.h-36{ + height: 9rem; +} + .min-h-\[600px\]{ min-height: 600px; } -.w-24{ - width: 6rem; +.min-h-\[400px\]{ + min-height: 400px; +} + +.min-h-\[200px\]{ + min-height: 200px; } .w-3{ width: 0.75rem; } +.w-32{ + width: 8rem; +} + .w-56{ width: 14rem; } -.w-96{ +.w-\[24rem\]{ width: 24rem; } -.w-\[24rem\]{ - width: 24rem; +.w-\[32rem\]{ + width: 32rem; } .w-\[550px\]{ @@ -2644,18 +2698,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-\[32rem\]{ - width: 32rem; -} - -.w-32{ - width: 8rem; -} - -.w-\[400px\]{ - width: 400px; -} - .min-w-\[300px\]{ min-width: 300px; } @@ -2678,6 +2720,10 @@ details.collapse summary::-webkit-details-marker{ cursor: pointer; } +.resize-none{ + resize: none; +} + .flex-row{ flex-direction: row; } @@ -2778,6 +2824,26 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } +.rounded-t-md{ + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; +} + +.rounded-b-none{ + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + +.rounded-t-none{ + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + .border{ border-width: 1px; } @@ -2790,6 +2856,18 @@ details.collapse summary::-webkit-details-marker{ border-top-width: 4px; } +.border-r{ + border-right-width: 1px; +} + +.border-b-0{ + border-bottom-width: 0px; +} + +.border-t-0{ + border-top-width: 0px; +} + .border-dashed{ border-style: dashed; } @@ -2803,6 +2881,16 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(249 115 22 / var(--tw-border-opacity)); } +.border-gray-500{ + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); +} + +.border-gray-300{ + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + .border-black{ --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -2909,10 +2997,6 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } -.p-10{ - padding: 2.5rem; -} - .p-2{ padding: 0.5rem; } @@ -2950,11 +3034,6 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } -.py-4{ - padding-top: 1rem; - padding-bottom: 1rem; -} - .text-start{ text-align: start; } @@ -3255,6 +3334,11 @@ img[src] { color: rgb(239 68 68 / var(--tw-text-opacity)); } +.focus\:outline-none:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + @media (min-width: 640px){ .sm\:flex{ display: flex; From f06d7a8e21e1419086af698aa637a7e7440c2443 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 11:19:10 +0800 Subject: [PATCH 055/127] feat: load workflow when switch tab --- js/AvatarPreview.js | 43 +++++++++++++++++++++++++++++-------------- routes.py | 31 ++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 4882843..b8511c6 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -16,8 +16,8 @@ import { uploadSegments } from "./LayerEditor.js"; import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; -async function loadJSONWorkflow() { - const json = await (await fetch("./get_default_workflow")).json(); +async function loadJSONWorkflow(name) { + const json = await (await fetch(`./get_workflow?name=${name}`)).json(); app.loadGraphData(json); console.log(json); } @@ -102,7 +102,7 @@ async function prepareImageFromUrlRedirect(stage) { export function AvatarPreview() { console.log("getting workflow json now"); - loadJSONWorkflow().then(() => { + loadJSONWorkflow("default").then(() => { console.log("done loading"); jsonWorkflowLoading.val = false; }); @@ -179,6 +179,13 @@ export function AvatarPreview() { class: () => "tab w-full", checked: true, ariaLabel: "Custom avatar", + onclick: () => { + jsonWorkflowLoading.val = true; + loadJSONWorkflow("default").then(() => { + console.log("done loading"); + jsonWorkflowLoading.val = false; + }); + }, }), div( { class: () => "tab-content text-black w-full" }, @@ -311,6 +318,13 @@ export function AvatarPreview() { name: "my_tabs_1", class: "tab", ariaLabel: "Generate avatar", + onclick: () => { + jsonWorkflowLoading.val = true; + loadJSONWorkflow("Lora").then(() => { + console.log("done loading"); + jsonWorkflowLoading.val = false; + }); + }, }), div( { class: "tab-content text-black w-full" }, @@ -536,17 +550,18 @@ export function AvatarPreview() { class: () => "btn text-black flex flex-row btn-ghost normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", onclick: () => { - let input = document.createElement("input"); - input.type = "file"; - document.body.appendChild(input); - input.accept = ".json,image/png,.latent,.safetensors"; - input.onchange = async function (e) { - if (Object.entries(e.target.files).length) { - app.handleFile(e.target.files[0]); - } - document.body.removeChild(input); - }; - input.click(); + fetch(""); + // let input = document.createElement("input"); + // input.type = "file"; + // document.body.appendChild(input); + // input.accept = ".json,image/png,.latent,.safetensors"; + // input.onchange = async function (e) { + // if (Object.entries(e.target.files).length) { + // app.handleFile(e.target.files[0]); + // } + // document.body.removeChild(input); + // }; + // input.click(); // document.getElementById("comfy-load-button").click(); }, }, diff --git a/routes.py b/routes.py index 31c5755..9280035 100644 --- a/routes.py +++ b/routes.py @@ -99,15 +99,28 @@ async def post_sam_model(request): return web.json_response({}) -@server.PromptServer.instance.routes.get("/get_default_workflow") -async def get_default_workflow(request): - # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow - # json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" # Benny workflow - # json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173536418337914970/newstyle.json?ex=65644ff5&is=6551daf5&hm=f129838fae10197351bd27c69c7ff5eb4edf2c7d6ed74e6db8b55ddaa3c77dee&' # Deepwoo workflow - json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' - response = requests.get(json_link) - response.raise_for_status() - return web.json_response(response.json()) +# @server.PromptServer.instance.routes.get("/get_default_workflow") +# async def get_default_workflow(request): +# # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow +# # json_link = "https://cdn.discordapp.com/attachments/729003657483518063/1172504658812608572/workflow_15.json?ex=65608f0e&is=654e1a0e&hm=f707d887b9294c1e9b26e54856b1e516d1725a1b25d044b46229cea6e5c804a1&" # Benny workflow +# # json_link = 'https://cdn.discordapp.com/attachments/1110859802701221898/1173536418337914970/newstyle.json?ex=65644ff5&is=6551daf5&hm=f129838fae10197351bd27c69c7ff5eb4edf2c7d6ed74e6db8b55ddaa3c77dee&' # Deepwoo workflow +# json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' +# response = requests.get(json_link) +# response.raise_for_status() +# return web.json_response(response.json()) + +@server.PromptServer.instance.routes.get("/get_workflow") +async def get_workflow(request): + name = request.rel_url.query.get("name", "default") + if name == "default": + json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' + response = requests.get(json_link) + response.raise_for_status() + workflow = response.json() + else: + workflows_path = os.path.join(os.path.dirname(__file__), "workflow_templates") + workflow = json.load(open(f"{workflows_path}/{name}.json")) + return web.json_response(workflow) @server.PromptServer.instance.routes.post("/segments") From 60925ec745d29ea0e6b826cb0392fb171a5e7180 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 11:34:14 +0800 Subject: [PATCH 056/127] feat: random seed button --- js/AvatarPreview.js | 83 +++++++++++++++++++++++++++++++-------------- js/tw-styles.css | 13 +++++++ 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index b8511c6..7e50f97 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -22,6 +22,34 @@ async function loadJSONWorkflow(name) { console.log(json); } +async function updatePositivePrompt(app, prompt) { + const positivePrompt = app.graph + .findNodesByType("CLIPTextEncode") + .find((x) => x.color == "#232"); + if (!positivePrompt) { + alertDialog.val = { + text: "Cannot find the CLIPTextEncode node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } + + positivePrompt.widgets[0].inputEl.value = prompt; +} + +async function updateSeedValue(app, seed) { + const kSampler = app.graph.findNodesByType("KSampler")[0]; + if (!kSampler) { + alertDialog.val = { + text: "Cannot find the KSampler node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } + kSampler.widgets[0].value = seed; + kSampler.widgets[1].value = "fixed"; +} + async function uploadImage() { /** @type {import('../../../web/types/litegraph.js').LGraph}*/ const graph = app.graph; @@ -356,40 +384,43 @@ export function AvatarPreview() { placeholder: "Seed", defaultValue: "1234", id: "seedProxy", - }) + }), + div( + { + onclick: () => { + const random4Digits = + Math.floor(Math.random() * 9000) + 1000; + console.log( + random4Digits, + document.getElementById("seedProxy").value + ); + document.getElementById("seedProxy").value = + random4Digits.toString(); + }, + }, + span({ + class: "iconify text-2xl mr-4 hover:cursor-pointer", + "data-icon": "fad:random-1dice", + "data-inline": "false", + }) + ) ) ), button( { class: "btn w-full normal-case ", onclick: async () => { - const positivePrompt = app.graph - .findNodesByType("CLIPTextEncode") - .find((x) => x.color == "#232"); - if (!positivePrompt) { - alertDialog.val = { - text: "Cannot find the CLIPTextEncode node. Please make sure the workflow is correct.", - time: 5000, - }; - return; - } - const kSampler = app.graph.findNodesByType("KSampler")[0]; - if (!kSampler) { - alertDialog.val = { - text: "Cannot find the KSampler node. Please make sure the workflow is correct.", - time: 5000, - }; - return; - } - - positivePrompt.widgets[0].inputEl.value = - document.getElementById("positivePromptProxy").value; - kSampler.widgets[0].value = - document.getElementById("seedProxy").value; - kSampler.widgets[1].value = "fixed"; - loading.val = true; + updatePositivePrompt( + app, + document.getElementById("positivePromptProxy").value + ); + updateSeedValue( + app, + document.getElementById("seedProxy").value + ); + const sam = app.graph.findNodesByType("SAM MultiLayer")[0]; if (!sam) { alertDialog.val = { diff --git a/js/tw-styles.css b/js/tw-styles.css index 6e03c7a..c1efcf6 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2612,6 +2612,10 @@ details.collapse summary::-webkit-details-marker{ margin-left: 1rem; } +.mr-4{ + margin-right: 1rem; +} + .block{ display: block; } @@ -3067,6 +3071,11 @@ details.collapse summary::-webkit-details-marker{ line-height: 1rem; } +.text-3xl{ + font-size: 1.875rem; + line-height: 2.25rem; +} + .font-bold{ font-weight: 700; } @@ -3329,6 +3338,10 @@ img[src] { } } +.hover\:cursor-pointer:hover{ + cursor: pointer; +} + .hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); From 6c90e5fa5f9ef66586eaf4762377a6a24ab7eb3b Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 11:48:34 +0800 Subject: [PATCH 057/127] chore: update mobile view width threshold --- js/AvatarPreview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 7e50f97..e657833 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -314,7 +314,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit segment" + "Edit Segment" ), button( { @@ -337,7 +337,7 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md", }) - : "Make it alive!" + : "Make It Alive!" ) ) ), @@ -619,7 +619,7 @@ export function AvatarPreview() { }; const isMobileDevice = () => { - return window.screen.width < 640; + return window.screen.width < 768; }; return div( From bbe89107cc4c512b4c063045ccb312fb585f65ca Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 12:01:29 +0800 Subject: [PATCH 058/127] app ui update --- js/AvatarPreview.js | 41 ++++++----------- js/tw-styles.css | 110 ++++++++++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 71 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 4882843..02d5dd0 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -71,12 +71,12 @@ async function prepareImageFromUrlRedirect(stage) { dragndrop.addEventListener("dragenter", (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-purple-500 text-purple-500 border-dashed rounded-lg flex justify-center items-center"; + "h-96 w-full border-2 border-purple-500 text-purple-500 border-dashed rounded-lg flex justify-center items-center"; }); dragndrop.addEventListener("dragleave", (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; }); dragndrop.addEventListener("dragover", (evt) => { evt.preventDefault(); @@ -84,7 +84,7 @@ async function prepareImageFromUrlRedirect(stage) { dragndrop.addEventListener("drop", async (evt) => { evt.preventDefault(); dragndrop.className = - "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center"; if (evt.dataTransfer.files.length > 1) return; if ( evt.dataTransfer.files[0].type != "image/jpeg" && @@ -191,9 +191,7 @@ export function AvatarPreview() { button( { class: () => - `${ - previewImg.val != "" && "w-full" - } h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, + `w-full h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, onclick: async () => { // previewImg.val = await uploadImage(); // stage.val = 1; @@ -230,33 +228,22 @@ export function AvatarPreview() { class: "loading loading-spinner loading-md", }) : "" - ), - previewImg.val == "" - ? div( - { class: () => "divider divider-horizontal !gap-0" }, - "OR" - ) - : "", - previewImg.val == "" + ) + ), + () => previewImg.val == "" ? div( { id: "dnd", class: () => - "w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", }, - "Drag and drop the image here" + "or drag and drop the image here" ) - : "" - ), - () => - previewImg.val != "" - ? img({ - class: () => - "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }) - : "", - + : img({ + class: () => + "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }), button( { class: () => diff --git a/js/tw-styles.css b/js/tw-styles.css index 6e03c7a..e310a77 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -730,6 +730,12 @@ html{ .tab:hover{ --tw-text-opacity: 1; } + + .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, + .tabs-boxed :is(input:checked):hover{ + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + } } .btn{ @@ -1533,12 +1539,6 @@ input.tab:checked + .tab-content, } } -.card.bordered{ - border-width: 1px; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); -} - @keyframes checkmark{ 0%{ background-position-y: 5px; @@ -2140,10 +2140,25 @@ details.collapse summary::-webkit-details-marker{ background-position: top right; } +.tabs-boxed{ + border-radius: var(--rounded-btn, 0.5rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding: 0.25rem; +} + .tabs-boxed .tab{ border-radius: var(--rounded-btn, 0.5rem); } +.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), + .tabs-boxed :is(input:checked){ + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2645,6 +2660,10 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } +.h-\[400px\]{ + height: 400px; +} + .h-24{ height: 6rem; } @@ -2653,6 +2672,10 @@ details.collapse summary::-webkit-details-marker{ height: 9rem; } +.h-96{ + height: 24rem; +} + .min-h-\[600px\]{ min-height: 600px; } @@ -2661,28 +2684,24 @@ details.collapse summary::-webkit-details-marker{ min-height: 400px; } -.min-h-\[200px\]{ - min-height: 200px; +.w-24{ + width: 6rem; } .w-3{ width: 0.75rem; } -.w-32{ - width: 8rem; -} - .w-56{ width: 14rem; } -.w-\[24rem\]{ +.w-96{ width: 24rem; } -.w-\[32rem\]{ - width: 32rem; +.w-\[24rem\]{ + width: 24rem; } .w-\[550px\]{ @@ -2698,6 +2717,18 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-\[32rem\]{ + width: 32rem; +} + +.w-32{ + width: 8rem; +} + +.w-\[400px\]{ + width: 400px; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2824,9 +2855,9 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } -.rounded-t-md{ - border-top-left-radius: 0.375rem; - border-top-right-radius: 0.375rem; +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; } .rounded-b-none{ @@ -2834,14 +2865,9 @@ details.collapse summary::-webkit-details-marker{ border-bottom-left-radius: 0px; } -.rounded-t-none{ - border-top-left-radius: 0px; - border-top-right-radius: 0px; -} - -.rounded-b-md{ - border-bottom-right-radius: 0.375rem; - border-bottom-left-radius: 0.375rem; +.rounded-t-md{ + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; } .border{ @@ -2856,18 +2882,10 @@ details.collapse summary::-webkit-details-marker{ border-top-width: 4px; } -.border-r{ - border-right-width: 1px; -} - .border-b-0{ border-bottom-width: 0px; } -.border-t-0{ - border-top-width: 0px; -} - .border-dashed{ border-style: dashed; } @@ -2881,16 +2899,6 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(249 115 22 / var(--tw-border-opacity)); } -.border-gray-500{ - --tw-border-opacity: 1; - border-color: rgb(107 114 128 / var(--tw-border-opacity)); -} - -.border-gray-300{ - --tw-border-opacity: 1; - border-color: rgb(209 213 219 / var(--tw-border-opacity)); -} - .border-black{ --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -2901,6 +2909,11 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } +.border-gray-300{ + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + .bg-\[\#f6f6f6\]{ --tw-bg-opacity: 1; background-color: rgb(246 246 246 / var(--tw-bg-opacity)); @@ -2997,6 +3010,10 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.p-10{ + padding: 2.5rem; +} + .p-2{ padding: 0.5rem; } @@ -3034,6 +3051,11 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } +.py-4{ + padding-top: 1rem; + padding-bottom: 1rem; +} + .text-start{ text-align: start; } From 48297b566c21d15258e4c7ac1eb2e88e5d5e1d12 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 12:15:00 +0800 Subject: [PATCH 059/127] fix: mobile view dnd --- js/AvatarPreview.js | 35 ++++++++------ js/tw-styles.css | 110 +++++++++++++++++++------------------------- 2 files changed, 69 insertions(+), 76 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 2261716..18bd3bc 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -155,7 +155,8 @@ export function AvatarPreview() { const renderSteps = () => { return div( { - class: () => "flex flex-col bg-white justify-center w-[32rem]", + class: () => + "flex flex-col bg-white justify-center w-[32rem] max-w-[100%]", }, div( { @@ -265,20 +266,26 @@ export function AvatarPreview() { : "" ) ), - () => previewImg.val == "" - ? div( - { - id: "dnd", - class: () => - "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", - }, - "or drag and drop the image here" - ) - : img({ + () => { + const dnd = div( + { + id: "dnd", class: () => - "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }), + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", + }, + "or drag and drop the image here" + ); + const image = img({ + class: () => "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }); + + if (isMobileDevice()) { + if (previewImg.val) return image; + } else { + return previewImg.val === "" ? dnd : image; + } + }, button( { class: () => diff --git a/js/tw-styles.css b/js/tw-styles.css index b29214a..5ff998d 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -730,12 +730,6 @@ html{ .tab:hover{ --tw-text-opacity: 1; } - - .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, - .tabs-boxed :is(input:checked):hover{ - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); - } } .btn{ @@ -1539,6 +1533,12 @@ input.tab:checked + .tab-content, } } +.card.bordered{ + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + @keyframes checkmark{ 0%{ background-position-y: 5px; @@ -2140,25 +2140,10 @@ details.collapse summary::-webkit-details-marker{ background-position: top right; } -.tabs-boxed{ - border-radius: var(--rounded-btn, 0.5rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - padding: 0.25rem; -} - .tabs-boxed .tab{ border-radius: var(--rounded-btn, 0.5rem); } -.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), - .tabs-boxed :is(input:checked){ - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2664,10 +2649,6 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } -.h-\[400px\]{ - height: 400px; -} - .h-24{ height: 6rem; } @@ -2688,24 +2669,28 @@ details.collapse summary::-webkit-details-marker{ min-height: 400px; } -.w-24{ - width: 6rem; +.min-h-\[200px\]{ + min-height: 200px; } .w-3{ width: 0.75rem; } +.w-32{ + width: 8rem; +} + .w-56{ width: 14rem; } -.w-96{ +.w-\[24rem\]{ width: 24rem; } -.w-\[24rem\]{ - width: 24rem; +.w-\[32rem\]{ + width: 32rem; } .w-\[550px\]{ @@ -2721,22 +2706,14 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-\[32rem\]{ - width: 32rem; -} - -.w-32{ - width: 8rem; -} - -.w-\[400px\]{ - width: 400px; -} - .min-w-\[300px\]{ min-width: 300px; } +.max-w-\[100\%\]{ + max-width: 100%; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2859,9 +2836,9 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } -.rounded-b-md{ - border-bottom-right-radius: 0.375rem; - border-bottom-left-radius: 0.375rem; +.rounded-t-md{ + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; } .rounded-b-none{ @@ -2869,9 +2846,14 @@ details.collapse summary::-webkit-details-marker{ border-bottom-left-radius: 0px; } -.rounded-t-md{ - border-top-left-radius: 0.375rem; - border-top-right-radius: 0.375rem; +.rounded-t-none{ + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; } .border{ @@ -2886,10 +2868,18 @@ details.collapse summary::-webkit-details-marker{ border-top-width: 4px; } +.border-r{ + border-right-width: 1px; +} + .border-b-0{ border-bottom-width: 0px; } +.border-t-0{ + border-top-width: 0px; +} + .border-dashed{ border-style: dashed; } @@ -2903,6 +2893,16 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(249 115 22 / var(--tw-border-opacity)); } +.border-gray-500{ + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); +} + +.border-gray-300{ + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + .border-black{ --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -2913,11 +2913,6 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } -.border-gray-300{ - --tw-border-opacity: 1; - border-color: rgb(209 213 219 / var(--tw-border-opacity)); -} - .bg-\[\#f6f6f6\]{ --tw-bg-opacity: 1; background-color: rgb(246 246 246 / var(--tw-bg-opacity)); @@ -3014,10 +3009,6 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } -.p-10{ - padding: 2.5rem; -} - .p-2{ padding: 0.5rem; } @@ -3055,11 +3046,6 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } -.py-4{ - padding-top: 1rem; - padding-bottom: 1rem; -} - .text-start{ text-align: start; } From 386f87a1e3fb588656422b0fabbdb0a5f17f774f Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 12:19:45 +0800 Subject: [PATCH 060/127] hide header in comfy view --- js/Container.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/Container.js b/js/Container.js index 78bd798..8130d57 100644 --- a/js/Container.js +++ b/js/Container.js @@ -17,7 +17,6 @@ export function Container() { LayerEditor(), AvatarPreview(), Loading(), - Alert(), - AppHeader() + Alert() ); } From 9456b37477ceb152dbba2496bac941d23194080e Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 15 Nov 2023 23:28:55 -0500 Subject: [PATCH 061/127] fix: phone layer editor pos and neg point --- js/LayerEditor.js | 11 +++- js/index.js | 10 ++-- js/tw-styles.css | 127 ++++++++++++++++------------------------------ 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index d41e610..4f5662f 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -19,6 +19,7 @@ import { van } from "./van.js"; const { button, div, img, canvas, span } = van.tags; let throttle = false; +const positivePrompt = van.state(true); export function updateImagePrompts() { if (selectedLayer.val !== "" && selectedLayer.val !== undefined) { @@ -94,7 +95,7 @@ async function handleClick(e) { imagePrompts.val = [ ...imagePrompts.val, - { x: relativeX, y: relativeY, label: e.isRight ? 0 : 1 }, + { x: relativeX, y: relativeY, label: !positivePrompt.val ? 0 : 1 }, ]; await drawSegment(getClicks()); updateImagePrompts(); @@ -198,6 +199,14 @@ export function LayerEditor() { }, div(() => showSidebar.val ? "Hide UI" : "Show UI") ), + button( + { + class: () => + "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit", + onclick: () => positivePrompt.val = !positivePrompt.val + }, + div(() => positivePrompt.val ? "Positive" : "Negative") + ), div( { class: diff --git a/js/index.js b/js/index.js index 34eef3d..eba03fe 100644 --- a/js/index.js +++ b/js/index.js @@ -695,9 +695,8 @@ export async function uploadPreview() { }).catch((error) => console.error(error)); infoDialog.show( - `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + - labData.modelId + - `` + `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=${labData.modelId}` + + `\nChat url: https://labs.avatech.ai?avatarId=${labData.modelId}` ); previewModelId.val = labData.modelId; } @@ -812,9 +811,8 @@ function injectUIComponentToComfyuimenu() { shareAvatar.append(dropdown); } else { infoDialog.show( - `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=` + - previewModelId.val + - `` + `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=${previewModelId.val}` + + `\nChat url: https://labs.avatech.ai?avatarId=${previewModelId.val}` ); } }; diff --git a/js/tw-styles.css b/js/tw-styles.css index 5ff998d..bcb1ed7 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1533,12 +1533,6 @@ input.tab:checked + .tab-content, } } -.card.bordered{ - border-width: 1px; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); -} - @keyframes checkmark{ 0%{ background-position-y: 5px; @@ -2551,6 +2545,14 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } +.left-52{ + left: 13rem; +} + +.left-56{ + left: 14rem; +} + .z-\[1000\]{ z-index: 1000; } @@ -2592,6 +2594,14 @@ details.collapse summary::-webkit-details-marker{ margin-left: 0.5rem; } +.ml-4{ + margin-left: 1rem; +} + +.mr-4{ + margin-right: 1rem; +} + .mt-2{ margin-top: 0.5rem; } @@ -2608,14 +2618,6 @@ details.collapse summary::-webkit-details-marker{ margin-top: 2rem; } -.ml-4{ - margin-left: 1rem; -} - -.mr-4{ - margin-right: 1rem; -} - .block{ display: block; } @@ -2636,6 +2638,14 @@ details.collapse summary::-webkit-details-marker{ height: 0.75rem; } +.h-36{ + height: 9rem; +} + +.h-96{ + height: 24rem; +} + .h-\[394px\]{ height: 394px; } @@ -2649,30 +2659,10 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } -.h-24{ - height: 6rem; -} - -.h-36{ - height: 9rem; -} - -.h-96{ - height: 24rem; -} - -.min-h-\[600px\]{ - min-height: 600px; -} - .min-h-\[400px\]{ min-height: 400px; } -.min-h-\[200px\]{ - min-height: 200px; -} - .w-3{ width: 0.75rem; } @@ -2772,10 +2762,6 @@ details.collapse summary::-webkit-details-marker{ gap: 1rem; } -.\!gap-0{ - gap: 0px !important; -} - .place-self-end{ place-self: end; } @@ -2816,6 +2802,16 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.125rem; } +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-b-none{ + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + .rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -2841,21 +2837,6 @@ details.collapse summary::-webkit-details-marker{ border-top-right-radius: 0.375rem; } -.rounded-b-none{ - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; -} - -.rounded-t-none{ - border-top-left-radius: 0px; - border-top-right-radius: 0px; -} - -.rounded-b-md{ - border-bottom-right-radius: 0.375rem; - border-bottom-left-radius: 0.375rem; -} - .border{ border-width: 1px; } @@ -2864,20 +2845,12 @@ details.collapse summary::-webkit-details-marker{ border-width: 2px; } -.border-t-4{ - border-top-width: 4px; -} - -.border-r{ - border-right-width: 1px; -} - .border-b-0{ border-bottom-width: 0px; } -.border-t-0{ - border-top-width: 0px; +.border-t-4{ + border-top-width: 4px; } .border-dashed{ @@ -2888,14 +2861,9 @@ details.collapse summary::-webkit-details-marker{ border-style: none; } -.border-orange-500{ +.border-black{ --tw-border-opacity: 1; - border-color: rgb(249 115 22 / var(--tw-border-opacity)); -} - -.border-gray-500{ - --tw-border-opacity: 1; - border-color: rgb(107 114 128 / var(--tw-border-opacity)); + border-color: rgb(0 0 0 / var(--tw-border-opacity)); } .border-gray-300{ @@ -2903,9 +2871,9 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(209 213 219 / var(--tw-border-opacity)); } -.border-black{ +.border-orange-500{ --tw-border-opacity: 1; - border-color: rgb(0 0 0 / var(--tw-border-opacity)); + border-color: rgb(249 115 22 / var(--tw-border-opacity)); } .border-purple-500{ @@ -3079,11 +3047,6 @@ details.collapse summary::-webkit-details-marker{ line-height: 1rem; } -.text-3xl{ - font-size: 1.875rem; - line-height: 2.25rem; -} - .font-bold{ font-weight: 700; } @@ -3121,6 +3084,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(194 65 12 / var(--tw-text-opacity)); } +.text-purple-500{ + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + .text-transparent{ color: transparent; } @@ -3130,11 +3098,6 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-purple-500{ - --tw-text-opacity: 1; - color: rgb(168 85 247 / var(--tw-text-opacity)); -} - .opacity-80{ opacity: 0.8; } From 89c5432677c263042abc43b30c5c5c62ce0fc16a Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 15 Nov 2023 23:39:33 -0500 Subject: [PATCH 062/127] feat: switch clicks in mobile --- js/LayerEditor.js | 29 ++++++++++++++++++++--------- js/tw-styles.css | 8 ++++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 4f5662f..be820a7 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -20,6 +20,9 @@ const { button, div, img, canvas, span } = van.tags; let throttle = false; const positivePrompt = van.state(true); +const isMobileDevice = () => { + return window.screen.width < 768; +}; export function updateImagePrompts() { if (selectedLayer.val !== "" && selectedLayer.val !== undefined) { @@ -93,9 +96,16 @@ async function handleClick(e) { imageSize.val.imgScale ); + let label; + if (isMobileDevice()) { + label = positivePrompt.val ? 1 : 0; + } else { + label = e.isRight ? 0 : 1; + } + imagePrompts.val = [ ...imagePrompts.val, - { x: relativeX, y: relativeY, label: !positivePrompt.val ? 0 : 1 }, + { x: relativeX, y: relativeY, label }, ]; await drawSegment(getClicks()); updateImagePrompts(); @@ -148,7 +158,7 @@ export async function drawSegment(clicks) { export function LayerEditor() { let realTimeSegment = true; - const showSidebar = van.state(true) + const showSidebar = van.state(true); document.addEventListener("keydown", (e) => { if (showImageEditor.val && e.code === "Tab") { @@ -194,18 +204,19 @@ export function LayerEditor() { { class: () => "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-28 top-0 z-[200] w-fit", - onclick: () => showSidebar.val = !showSidebar.val - , + onclick: () => (showSidebar.val = !showSidebar.val), }, - div(() => showSidebar.val ? "Hide UI" : "Show UI") + div(() => (showSidebar.val ? "Hide UI" : "Show UI")) ), button( { class: () => - "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit", - onclick: () => positivePrompt.val = !positivePrompt.val + `btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit ${ + isMobileDevice() ? "" : "hidden" + }`, + onclick: () => (positivePrompt.val = !positivePrompt.val), }, - div(() => positivePrompt.val ? "Positive" : "Negative") + div(() => (positivePrompt.val ? "Positive" : "Negative")) ), div( { @@ -334,6 +345,6 @@ export function LayerEditor() { }) ) ), - () => showSidebar.val ? SideBar() : div() + () => (showSidebar.val ? SideBar() : div()) ); } diff --git a/js/tw-styles.css b/js/tw-styles.css index bcb1ed7..65ea0b0 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2517,6 +2517,10 @@ details.collapse summary::-webkit-details-marker{ left: 1rem; } +.left-56{ + left: 14rem; +} + .right-0{ right: 0px; } @@ -2549,10 +2553,6 @@ details.collapse summary::-webkit-details-marker{ left: 13rem; } -.left-56{ - left: 14rem; -} - .z-\[1000\]{ z-index: 1000; } From 35070550337862d42dbbcfb3688013e15758cd6a Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 13:49:24 +0800 Subject: [PATCH 063/127] fix: preview disappear issue --- js/AvatarPreview.js | 12 +-- js/tw-styles.css | 174 ++++++++++++++++++++++++++++++-------------- 2 files changed, 124 insertions(+), 62 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 18bd3bc..29e9675 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -201,11 +201,11 @@ export function AvatarPreview() { " w-full flex flex-col justify-center items-center gap-4", }, div( - { class: () => "tabs tabs-lifted tabs-lg w-full" }, + { class: () => "tabs tabs-lifted w-full" }, input({ type: "radio", name: "my_tabs_1", - class: () => "tab w-full", + class: () => "tab w-40", checked: true, ariaLabel: "Custom avatar", onclick: () => { @@ -220,14 +220,14 @@ export function AvatarPreview() { { class: () => "tab-content text-black w-full" }, div( { - class: () => "flex flex-col justify-center items-center gap-4", + class: () => "flex flex-col justify-center items-center gap-4 ", }, div( { class: () => "w-full flex mt-2" }, button( { class: () => - `w-full h-full btn flex flex-row normal-case px-4 rounded-md left-0 top-0 z-[200] pointer-events-auto`, + `btn w-full normal-case`, onclick: async () => { // previewImg.val = await uploadImage(); // stage.val = 1; @@ -281,7 +281,7 @@ export function AvatarPreview() { }); if (isMobileDevice()) { - if (previewImg.val) return image; + return previewImg.val !== "" ? image : ""; } else { return previewImg.val === "" ? dnd : image; } @@ -338,7 +338,7 @@ export function AvatarPreview() { input({ type: "radio", name: "my_tabs_1", - class: "tab", + class: "tab w-40", ariaLabel: "Generate avatar", onclick: () => { jsonWorkflowLoading.val = true; diff --git a/js/tw-styles.css b/js/tw-styles.css index 65ea0b0..5347056 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -730,6 +730,12 @@ html{ .tab:hover{ --tw-text-opacity: 1; } + + .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, + .tabs-boxed :is(input:checked):hover{ + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + } } .btn{ @@ -2134,10 +2140,25 @@ details.collapse summary::-webkit-details-marker{ background-position: top right; } +.tabs-boxed{ + border-radius: var(--rounded-btn, 0.5rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding: 0.25rem; +} + .tabs-boxed .tab{ border-radius: var(--rounded-btn, 0.5rem); } +.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), + .tabs-boxed :is(input:checked){ + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2517,10 +2538,6 @@ details.collapse summary::-webkit-details-marker{ left: 1rem; } -.left-56{ - left: 14rem; -} - .right-0{ right: 0px; } @@ -2594,14 +2611,6 @@ details.collapse summary::-webkit-details-marker{ margin-left: 0.5rem; } -.ml-4{ - margin-left: 1rem; -} - -.mr-4{ - margin-right: 1rem; -} - .mt-2{ margin-top: 0.5rem; } @@ -2618,6 +2627,14 @@ details.collapse summary::-webkit-details-marker{ margin-top: 2rem; } +.ml-4{ + margin-left: 1rem; +} + +.mr-4{ + margin-right: 1rem; +} + .block{ display: block; } @@ -2638,14 +2655,6 @@ details.collapse summary::-webkit-details-marker{ height: 0.75rem; } -.h-36{ - height: 9rem; -} - -.h-96{ - height: 24rem; -} - .h-\[394px\]{ height: 394px; } @@ -2659,28 +2668,48 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } +.h-\[400px\]{ + height: 400px; +} + +.h-24{ + height: 6rem; +} + +.h-36{ + height: 9rem; +} + +.h-96{ + height: 24rem; +} + +.min-h-\[600px\]{ + min-height: 600px; +} + .min-h-\[400px\]{ min-height: 400px; } +.w-24{ + width: 6rem; +} + .w-3{ width: 0.75rem; } -.w-32{ - width: 8rem; -} - .w-56{ width: 14rem; } -.w-\[24rem\]{ +.w-96{ width: 24rem; } -.w-\[32rem\]{ - width: 32rem; +.w-\[24rem\]{ + width: 24rem; } .w-\[550px\]{ @@ -2696,6 +2725,26 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } +.w-\[32rem\]{ + width: 32rem; +} + +.w-32{ + width: 8rem; +} + +.w-\[400px\]{ + width: 400px; +} + +.w-40{ + width: 10rem; +} + +.w-36{ + width: 9rem; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2762,6 +2811,10 @@ details.collapse summary::-webkit-details-marker{ gap: 1rem; } +.\!gap-0{ + gap: 0px !important; +} + .place-self-end{ place-self: end; } @@ -2802,16 +2855,6 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.125rem; } -.rounded-b-md{ - border-bottom-right-radius: 0.375rem; - border-bottom-left-radius: 0.375rem; -} - -.rounded-b-none{ - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; -} - .rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -2832,6 +2875,16 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-b-none{ + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + .rounded-t-md{ border-top-left-radius: 0.375rem; border-top-right-radius: 0.375rem; @@ -2845,14 +2898,14 @@ details.collapse summary::-webkit-details-marker{ border-width: 2px; } -.border-b-0{ - border-bottom-width: 0px; -} - .border-t-4{ border-top-width: 4px; } +.border-b-0{ + border-bottom-width: 0px; +} + .border-dashed{ border-style: dashed; } @@ -2861,26 +2914,26 @@ details.collapse summary::-webkit-details-marker{ border-style: none; } -.border-black{ - --tw-border-opacity: 1; - border-color: rgb(0 0 0 / var(--tw-border-opacity)); -} - -.border-gray-300{ - --tw-border-opacity: 1; - border-color: rgb(209 213 219 / var(--tw-border-opacity)); -} - .border-orange-500{ --tw-border-opacity: 1; border-color: rgb(249 115 22 / var(--tw-border-opacity)); } +.border-black{ + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + .border-purple-500{ --tw-border-opacity: 1; border-color: rgb(168 85 247 / var(--tw-border-opacity)); } +.border-gray-300{ + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + .bg-\[\#f6f6f6\]{ --tw-bg-opacity: 1; background-color: rgb(246 246 246 / var(--tw-bg-opacity)); @@ -2977,6 +3030,10 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } +.p-10{ + padding: 2.5rem; +} + .p-2{ padding: 0.5rem; } @@ -3014,6 +3071,11 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } +.py-4{ + padding-top: 1rem; + padding-bottom: 1rem; +} + .text-start{ text-align: start; } @@ -3084,11 +3146,6 @@ details.collapse summary::-webkit-details-marker{ color: rgb(194 65 12 / var(--tw-text-opacity)); } -.text-purple-500{ - --tw-text-opacity: 1; - color: rgb(168 85 247 / var(--tw-text-opacity)); -} - .text-transparent{ color: transparent; } @@ -3098,6 +3155,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } +.text-purple-500{ + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + .opacity-80{ opacity: 0.8; } From f569a6d4303fd093a766a56b2e58f0f7e34ec2d0 Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 13:58:03 +0800 Subject: [PATCH 064/127] fix: mobile layout --- js/AvatarPreview.js | 2 +- js/tw-styles.css | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 29e9675..0aea528 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -201,7 +201,7 @@ export function AvatarPreview() { " w-full flex flex-col justify-center items-center gap-4", }, div( - { class: () => "tabs tabs-lifted w-full" }, + { class: () => "tabs tabs-lifted md:w-full" }, input({ type: "radio", name: "my_tabs_1", diff --git a/js/tw-styles.css b/js/tw-styles.css index 5347056..45fff12 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -3394,3 +3394,9 @@ img[src] { width: 2.5rem; } } + +@media (min-width: 768px){ + .md\:w-full{ + width: 100%; + } +} From 89f3df916f2efac36b028068b875bdf6df7222a3 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 14:41:47 +0800 Subject: [PATCH 065/127] fix: segment image with alpha channel --- sam/sam_multilayer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index a14bbe9..eecbe92 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -72,7 +72,10 @@ class SAMMultiLayer: global_predictor = predictor predictor = global_predictor - + + if image.shape[3] == 4: + image = image[:, :, :, :3] + emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" if not os.path.exists(emb_filename): image_np = (image[0].numpy() * 255).astype(np.uint8) From ea44cd7e7db28a6c8313a0c7f1fe0d64fd009028 Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 15:37:36 +0800 Subject: [PATCH 066/127] enable change workflow --- js/AvatarPreview.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 0aea528..c45c9ad 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -575,19 +575,18 @@ export function AvatarPreview() { class: () => "btn text-black flex flex-row btn-ghost normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", onclick: () => { - fetch(""); - // let input = document.createElement("input"); - // input.type = "file"; - // document.body.appendChild(input); - // input.accept = ".json,image/png,.latent,.safetensors"; - // input.onchange = async function (e) { - // if (Object.entries(e.target.files).length) { - // app.handleFile(e.target.files[0]); - // } - // document.body.removeChild(input); - // }; - // input.click(); - // document.getElementById("comfy-load-button").click(); + let input = document.createElement("input"); + input.type = "file"; + document.body.appendChild(input); + input.accept = ".json,image/png,.latent,.safetensors"; + input.onchange = async function (e) { + if (Object.entries(e.target.files).length) { + app.handleFile(e.target.files[0]); + } + document.body.removeChild(input); + }; + input.click(); + document.getElementById("comfy-load-button").click(); }, }, span({ From fceed10afd4584497f959850dd594eeb90b32dcb Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 15:39:25 +0800 Subject: [PATCH 067/127] fix: double call issue --- js/AvatarPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index c45c9ad..d2aa742 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -586,7 +586,7 @@ export function AvatarPreview() { document.body.removeChild(input); }; input.click(); - document.getElementById("comfy-load-button").click(); + // document.getElementById("comfy-load-button").click(); }, }, span({ From 825f120032641ba43cc67b3e73c1cf8e0629c03b Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 16:09:44 +0800 Subject: [PATCH 068/127] fix: multiple SAM node in workflow --- js/index.js | 44 ++++++++++++++++++++++++++++---------------- routes.py | 7 ++++--- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/js/index.js b/js/index.js index eba03fe..2d5ff99 100644 --- a/js/index.js +++ b/js/index.js @@ -26,7 +26,7 @@ import "https://code.iconify.design/3/3.1.0/iconify.min.js"; import { drawSegment, getClicks } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; -export let generatedImagePath = null; +export const generatedImages = {}; const stylesheet = document.createElement("link"); stylesheet.setAttribute("type", "text/css"); @@ -243,14 +243,30 @@ function getInputWidgetValue(node, inputIndex, widgetName) { } console.log(targetLink, nodea); - console.log(nodea.getInputNode(0, true)); - if (nodea.type !== "LoadImage") { - return; + if (nodea.type === "LoadImage") { + /** @type {string} */ + const isGeneratedImage = false; + return [ + isGeneratedImage, + nodea.widgets.find((x) => x.name === widgetName).value, + ]; } + const saveImageNodeLink = nodea.outputs + .find((x) => x.type === "IMAGE") + .links.find((link) => { + const targetLink = graph.links[link]; + const targetNode = graph._nodes_by_id[targetLink.target_id]; + if (targetNode.type === "SaveImage") { + return true; + } + }); + const saveImageNode = + graph._nodes_by_id[graph.links[saveImageNodeLink].target_id]; /** @type {string} */ - return nodea.widgets.find((x) => x.name === widgetName).value; + const isGeneratedImage = true; + return [isGeneratedImage, generatedImages[saveImageNode.id]]; } /** @@ -258,11 +274,10 @@ function getInputWidgetValue(node, inputIndex, widgetName) { * @param {LGraphNode} node */ function showMyImageEditor(node) { - let connectedImageFileName = - getInputWidgetValue(node, 0, "image") || generatedImagePath; + let [isGeneratedImage, connectedImageFileName] = getInputWidgetValue(node, 0, "image"); if (!connectedImageFileName) { alertDialog.val = { - text: "Please connect an image first", + text: "Please connect or generate an image first", time: 3000, }; return; @@ -288,6 +303,7 @@ function showMyImageEditor(node) { method: "POST", body: JSON.stringify({ image: connectedImageFileName, + isGeneratedImage, embedding_id: id, ckpt, }), @@ -310,8 +326,8 @@ function showMyImageEditor(node) { imagePrompts.val = v; } showImageEditor.val = true; - const isGeneratedImage = connectedImageFileName.startsWith("avatar"); - const subfolder = isGeneratedImage || split.length === 1 ? "" : split[0]; + const subfolder = + isGeneratedImage || split.length === 1 ? "" : split[0]; imageUrl.val = api.apiURL( `/view?filename=${encodeURIComponent(connectedImageFileName)}&type=${ isGeneratedImage ? "output" : "input" @@ -444,12 +460,8 @@ const ext = { api.addEventListener("executed", (evt) => { const images = evt.detail?.output.images; - if ( - images?.length > 0 && - images[0].type === "output" && - images[0].filename.startsWith("avatar") - ) { - generatedImagePath = images[0].filename; + if (images?.length > 0 && images[0].type === "output") { + generatedImages[evt.detail.node] = images[0].filename; } if (evt.detail?.output.gltfFilename) { const viewer = document.getElementById( diff --git a/routes.py b/routes.py index 9280035..24c7bef 100644 --- a/routes.py +++ b/routes.py @@ -58,8 +58,8 @@ async def get_sam_model(request): return web.FileResponse(filename) -def load_image(image): - if image.startswith("avatar"): +def load_image(image, is_generated_image): + if is_generated_image: image_path = f"{folder_paths.get_output_directory()}/{image}" else: image_path = folder_paths.get_annotated_filepath(image) @@ -73,13 +73,14 @@ def load_image(image): @server.PromptServer.instance.routes.post("/sam_model") async def post_sam_model(request): post = await request.json() + is_generated_image = post.get("isGeneratedImage") emb_id = post.get("embedding_id") ckpt = post.get("ckpt") ckpt = folder_paths.get_full_path("sams", ckpt) model_type = re.findall(r'vit_[lbh]', ckpt)[0] emb_filename = f"{folder_paths.get_output_directory()}/{emb_id}_{model_type}.npy" if not os.path.exists(emb_filename): - image = load_image(post.get("image")) + image = load_image(post.get("image"), is_generated_image) sam = sam_model_registry[model_type](checkpoint=ckpt) predictor = SamPredictor(sam) From 5e53b90986f9f6ef486919a5c726765b10967602 Mon Sep 17 00:00:00 2001 From: San Date: Thu, 16 Nov 2023 17:59:30 +0800 Subject: [PATCH 069/127] feat: change workflow update --- js/AvatarPreview.js | 262 ++++++++++++++++++++++++++------------------ js/state.js | 2 + js/tw-styles.css | 83 ++++++++++++++ 3 files changed, 241 insertions(+), 106 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index d2aa742..24836fd 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -7,8 +7,10 @@ import { previewImg, previewImgLoading, alertDialog, + isGenerateFlow, } from "./state.js"; -const { button, iframe, div, img, input, label, span, textarea } = van.tags; +const { button, iframe, div, img, input, label, span, textarea, ul, li } = + van.tags; import { app } from "./app.js"; import { uploadPreview } from "./index.js"; import { api } from "./api.js"; @@ -16,6 +18,13 @@ import { uploadSegments } from "./LayerEditor.js"; import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; +const workflowList = [ + "SilverWolfLora_withhand", + "SilverWolfLora", + "BronyaZaychikLora_withhand", + "BronyaZaychikLora", +]; + async function loadJSONWorkflow(name) { const json = await (await fetch(`./get_workflow?name=${name}`)).json(); app.loadGraphData(json); @@ -90,8 +99,8 @@ async function prepareImageFromUrlRedirect(stage) { graph.change(); previewImg.val = api.apiURL( `/view?filename=${encodeURIComponent( - imageName - )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}` + imageName, + )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}`, ); console.log(previewImg); } @@ -163,18 +172,18 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1" + "Avatech v1", ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone" + "Get your DALLE3 AI Personal Clone", ), // input({ // type: "file", - // class: "file-input file-input-bordered w-full w-full", + // class: "file-input file-input-bordered w-full", // }), // label( @@ -200,34 +209,17 @@ export function AvatarPreview() { class: () => " w-full flex flex-col justify-center items-center gap-4", }, - div( - { class: () => "tabs tabs-lifted md:w-full" }, - input({ - type: "radio", - name: "my_tabs_1", - class: () => "tab w-40", - checked: true, - ariaLabel: "Custom avatar", - onclick: () => { - jsonWorkflowLoading.val = true; - loadJSONWorkflow("default").then(() => { - console.log("done loading"); - jsonWorkflowLoading.val = false; - }); - }, - }), - div( - { class: () => "tab-content text-black w-full" }, - div( + !isGenerateFlow.val + ? div( { - class: () => "flex flex-col justify-center items-center gap-4 ", + class: () => + "flex flex-col justify-center items-center gap-4 w-full", }, div( { class: () => "w-full flex mt-2" }, button( { - class: () => - `btn w-full normal-case`, + class: () => `btn w-full normal-case`, onclick: async () => { // previewImg.val = await uploadImage(); // stage.val = 1; @@ -263,17 +255,17 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md", }) - : "" - ) + : "", + ), ), () => { const dnd = div( { id: "dnd", class: () => - "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center", + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center text-black", }, - "or drag and drop the image here" + "or drag and drop the image here", ); const image = img({ class: () => "z-[10] object-contain w-full h-[394px] border", @@ -308,7 +300,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit Segment" + "Edit Segment", ), button( { @@ -331,31 +323,18 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md", }) - : "Make It Alive!" - ) + : "Make It Alive!", + ), ) - ), - input({ - type: "radio", - name: "my_tabs_1", - class: "tab w-40", - ariaLabel: "Generate avatar", - onclick: () => { - jsonWorkflowLoading.val = true; - loadJSONWorkflow("Lora").then(() => { - console.log("done loading"); - jsonWorkflowLoading.val = false; - }); - }, - }), - div( - { class: "tab-content text-black w-full" }, - div( - { class: "flex flex-col justify-center items-center gap-4" }, + : div( + { + class: + "flex flex-col justify-center items-center gap-4 w-full text-black", + }, div( { class: - "w-full mt-2 flex flex-col rounded-md left-0 top-0 z-[200] flex", + "w-full mt-2 flex flex-col rounded-md left-0 top-0 z-[200]", }, textarea({ class: @@ -386,7 +365,7 @@ export function AvatarPreview() { Math.floor(Math.random() * 9000) + 1000; console.log( random4Digits, - document.getElementById("seedProxy").value + document.getElementById("seedProxy").value, ); document.getElementById("seedProxy").value = random4Digits.toString(); @@ -396,9 +375,9 @@ export function AvatarPreview() { class: "iconify text-2xl mr-4 hover:cursor-pointer", "data-icon": "fad:random-1dice", "data-inline": "false", - }) - ) - ) + }), + ), + ), ), button( { @@ -408,11 +387,11 @@ export function AvatarPreview() { updatePositivePrompt( app, - document.getElementById("positivePromptProxy").value + document.getElementById("positivePromptProxy").value, ); updateSeedValue( app, - document.getElementById("seedProxy").value + document.getElementById("seedProxy").value, ); const sam = app.graph.findNodesByType("SAM MultiLayer")[0]; @@ -435,7 +414,7 @@ export function AvatarPreview() { () => loading.val ? span({ class: "loading loading-spinner loading-md" }) - : "Make It Alive!" + : "Make It Alive!", ), button( { @@ -453,12 +432,10 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "(Optional) Edit Segment" - ) - ) - ) - ) - ) + "(Optional) Edit Segment", + ), + ), + ), ); }; @@ -488,7 +465,7 @@ export function AvatarPreview() { class: () => "w-full flex justify-center font-bold italic text-gray-500", }, - span("We are launching OpenAI Assistant API integration soon!") + span("We are launching OpenAI Assistant API integration soon!"), ), div( { class: () => "w-[24rem] flex justify-center items-center" }, @@ -529,9 +506,9 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md", }) - : "Get Avatar Link" - ) - ) + : "Get Avatar Link", + ), + ), ); }; @@ -548,7 +525,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "ic:round-close", "data-inline": "false", - }) + }), ); }; @@ -565,39 +542,112 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "mdi:restart", "data-inline": "false", - }) + }), ); }; const renderChangeWorkflowButton = () => { - return button( + return div( { class: () => - "btn text-black flex flex-row btn-ghost normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", - onclick: () => { - let input = document.createElement("input"); - input.type = "file"; - document.body.appendChild(input); - input.accept = ".json,image/png,.latent,.safetensors"; - input.onchange = async function (e) { - if (Object.entries(e.target.files).length) { - app.handleFile(e.target.files[0]); - } - document.body.removeChild(input); - }; - input.click(); - // document.getElementById("comfy-load-button").click(); - }, + "dropdown dropdown-hover left-0 top-0 z-[200] pointer-events-auto text-black ", }, - span({ - class: "iconify text-lg", - "data-icon": "ic:round-swap-vert", - "data-inline": "false", - }), - span({ class: "sm:flex hidden" }, () => - jsonWorkflowLoading.val ? "Loading" : "Change workflow" - ) + label( + { + class: () => + "btn flex flex-row btn-ghost normal-case rounded-md sm:btn-md btn-sm", + tabIndex: () => 0, + }, + span({ + class: "iconify text-lg", + "data-icon": "ic:round-swap-vert", + "data-inline": "false", + }), + span({ class: "sm:flex hidden" }, () => + jsonWorkflowLoading.val ? "Loading" : "Change workflow", + ), + ), + ul( + { + class: () => + "dropdown-content z-[200] menu p-2 shadow rounded-box w-64 bg-white", + tabIndex: () => 0, + }, + workflowList.map((val, index) => { + return li( + { + class: () => "p-4 btn btn-ghost items-start", + onclick: async (e) => { + e.preventDefault(); + await loadJSONWorkflow(""); + await new Promise((resolve) => setTimeout(resolve, 200)); + const kSampler = app.graph.findNodesByType("KSampler")[0]; + if (!kSampler) isGenerateFlow.val = false; + else isGenerateFlow.val = true; + }, + }, + () => val, + ); + }), + div({ class: () => "divider !my-0" }), + li( + { + class: () => "p-4 btn btn-ghost items-start", + onclick: (e) => { + let input = document.createElement("input"); + input.type = "file"; + document.body.appendChild(input); + input.accept = ".json,image/png,.latent,.safetensors"; + input.onchange = async function (e) { + if (Object.entries(e.target.files).length) { + await app.handleFile(e.target.files[0]); + } + await new Promise((resolve) => setTimeout(resolve, 200)); + const kSampler = app.graph.findNodesByType("KSampler")[0]; + if (!kSampler) isGenerateFlow.val = false; + else isGenerateFlow.val = true; + document.body.removeChild(input); + }; + input.click(); + // document.getElementById("comfy-load-button").click(); + }, + }, + "Import...", + ), + ), ); + // return button( + // { + // class: () => + // "btn text-black flex flex-row btn-ghost normal-case rounded-md left-0 top-0 z-[200] pointer-events-auto sm:btn-md btn-sm ", + // onclick: () => { + // let input = document.createElement("input"); + // input.type = "file"; + // document.body.appendChild(input); + // input.accept = ".json,image/png,.latent,.safetensors"; + // input.onchange = async function (e) { + // if (Object.entries(e.target.files).length) { + // await app.handleFile(e.target.files[0]); + // } + // await new Promise((resolve) => setTimeout(resolve, 200)); + // const kSampler = app.graph.findNodesByType("KSampler")[0]; + // if (!kSampler) isGenerateFlow.val = false; + // else isGenerateFlow.val = true; + // document.body.removeChild(input); + // }; + // input.click(); + // // document.getElementById("comfy-load-button").click(); + // }, + // }, + // span({ + // class: "iconify text-lg", + // "data-icon": "ic:round-swap-vert", + // "data-inline": "false", + // }), + // span({ class: "sm:flex hidden" }, () => + // jsonWorkflowLoading.val ? "Loading" : "Change workflow", + // ), + // ); }; const renderTwitter = () => { @@ -607,7 +657,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn sm:w-10 w-32 text-black btn-ghost text-xs !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter" + "Twitter", ); }; @@ -634,7 +684,7 @@ export function AvatarPreview() { }, renderCloseButton(), renderRestartButton(), - renderChangeWorkflowButton() + renderChangeWorkflowButton(), ), renderTwitter(), () => { @@ -647,7 +697,7 @@ export function AvatarPreview() { }, renderIFrame(), renderSteps(), - renderShareLink() + renderShareLink(), ); } else { return div( @@ -660,12 +710,12 @@ export function AvatarPreview() { div( { class: () => "flex flex-col" }, renderIFrame(), - renderShareLink() - ) + renderShareLink(), + ), ); } - } - ) + }, + ), ); } @@ -685,8 +735,8 @@ function showImage(name) { } img.src = api.apiURL( `/view?filename=${encodeURIComponent( - name - )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}` + name, + )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`, ); node.setSizeForImage?.(); } diff --git a/js/state.js b/js/state.js index 104e187..7925d41 100644 --- a/js/state.js +++ b/js/state.js @@ -33,6 +33,8 @@ export const alertDialog = van.state({ export const shareLoading = van.state(false); export const previewModelId = van.state(''); +export const isGenerateFlow = van.state(false) + export const loadingCaption = van.state(""); export const imageUrl = van.state(""); export const point_label = van.state(1); diff --git a/js/tw-styles.css b/js/tw-styles.css index 45fff12..fb745ab 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -913,6 +913,46 @@ html{ transition-duration: 200ms; } +.dropdown-end .dropdown-content{ + inset-inline-end: 0px; +} + +.dropdown-left .dropdown-content{ + bottom: auto; + inset-inline-end: 100%; + top: 0px; + transform-origin: right; +} + +.dropdown-right .dropdown-content{ + bottom: auto; + inset-inline-start: 100%; + top: 0px; + transform-origin: left; +} + +.dropdown-bottom .dropdown-content{ + bottom: auto; + top: 100%; + transform-origin: top; +} + +.dropdown-top .dropdown-content{ + bottom: 100%; + top: auto; + transform-origin: bottom; +} + +.dropdown-end.dropdown-right .dropdown-content{ + bottom: 0px; + top: auto; +} + +.dropdown-end.dropdown-left .dropdown-content{ + bottom: 0px; + top: auto; +} + .dropdown.dropdown-open .dropdown-content, .dropdown:not(.dropdown-hover):focus .dropdown-content, .dropdown:focus-within .dropdown-content{ @@ -2594,6 +2634,14 @@ details.collapse summary::-webkit-details-marker{ z-index: 99; } +.z-\[2000\]{ + z-index: 2000; +} + +.z-\[3000\]{ + z-index: 3000; +} + .m-0{ margin: 0px; } @@ -2603,6 +2651,11 @@ details.collapse summary::-webkit-details-marker{ margin-bottom: 0px; } +.\!my-0{ + margin-top: 0px !important; + margin-bottom: 0px !important; +} + .mb-2{ margin-bottom: 0.5rem; } @@ -2643,6 +2696,10 @@ details.collapse summary::-webkit-details-marker{ display: flex; } +.\!flex{ + display: flex !important; +} + .hidden{ display: none; } @@ -2745,6 +2802,14 @@ details.collapse summary::-webkit-details-marker{ width: 9rem; } +.w-52{ + width: 13rem; +} + +.w-64{ + width: 16rem; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2791,6 +2856,14 @@ details.collapse summary::-webkit-details-marker{ align-items: center; } +.justify-start{ + justify-content: flex-start; +} + +.\!justify-start{ + justify-content: flex-start !important; +} + .justify-center{ justify-content: center; } @@ -3080,6 +3153,10 @@ details.collapse summary::-webkit-details-marker{ text-align: start; } +.\!text-start{ + text-align: start !important; +} + .font-gabarito{ font-family: Gabarito; } @@ -3164,6 +3241,12 @@ details.collapse summary::-webkit-details-marker{ opacity: 0.8; } +.shadow{ + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .\!outline-none{ outline: 2px solid transparent !important; outline-offset: 2px !important; From 8fedf274354fec63e92983329bbd026bd6c06945 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 18:30:33 +0800 Subject: [PATCH 070/127] feat: auto segment --- js/LayerEditor.js | 174 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index be820a7..4eab270 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -16,6 +16,8 @@ import { alertDialog, } from "./state.js"; import { van } from "./van.js"; +import vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3"; +const { FaceLandmarker, FilesetResolver } = vision; const { button, div, img, canvas, span } = van.tags; let throttle = false; @@ -24,6 +26,173 @@ const isMobileDevice = () => { return window.screen.width < 768; }; +// Auto segmentation +const filesetResolver = await FilesetResolver.forVisionTasks( + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm" +); +const faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, { + baseOptions: { + modelAssetPath: `https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task`, + delegate: "GPU", + }, + outputFaceBlendshapes: true, + runningMode: "IMAGE", + numFaces: 1, +}); +const layerMapping = { + L_eye: { + useMiddle: false, + positiveOffset: 0.1, + negativeOffset: 0.5, + indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, + }, + R_eye: { + useMiddle: false, + positiveOffset: 0.1, + negativeOffset: 0.5, + indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, + }, + L_iris: { + useMiddle: false, + positiveOffset: 0, + negativeOffset: 0.1, + indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, + }, + R_iris: { + useMiddle: false, + positiveOffset: 0, + negativeOffset: 0.1, + indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, + }, + face: { + useMiddle: false, + positiveOffset: -0.1, + negativeOffset: 0.1, + indices: FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, + }, + mouth: { + useMiddle: true, + positiveOffset: 0, + negativeOffset: 0.2, + indices: FaceLandmarker.FACE_LANDMARKS_LIPS, + }, + mouth_in: { + useMiddle: true, + positiveOffset: 0, + negativeOffset: 0.2, + indices: FaceLandmarker.FACE_LANDMARKS_LIPS, + }, +}; + +async function drawPoints(part) {} + +async function autoSegment() { + const image = document.getElementById("image"); + const landmarks = faceLandmarker.detect(image).faceLandmarks[0]; + Object.entries(layerMapping).forEach(([key, value]) => { + imagePromptsMulti.val[key] = []; + }); + + Object.entries(layerMapping).forEach(([key, value]) => { + const positivePoints = []; + const middlePoints = []; + const negativePoints = []; + + // Positive points + for (const { start, end } of value.indices) { + const startPoint = landmarks[start]; + const endPoint = landmarks[end]; + + const startX = startPoint.x * imageSize.val.width; + const startY = startPoint.y * imageSize.val.height; + + // const endX = endPoint.x * imageSize.val.width; + // const endY = endPoint.y * imageSize.val.height; + + if (middlePoints.length === 0) { + middlePoints.push({ x: startX, y: startY, label: 1 }); + // middlePoints.push({ x: endX, y: endY, label: 1 }); + } else { + middlePoints[0].x += startX; + middlePoints[0].y += startY; + // middlePoints[1].x += endX; + // middlePoints[1].y += endY; + } + positivePoints.push({ x: startX, y: startY, label: 1 }); + // positivePoints.push({ x: endX, y: endY, label: 1 }); + + // imagePrompts.val = [...imagePrompts.val, { x, y, label: 1 }]; + } + + // Middle points + const len = value.indices.length; + middlePoints[0].x /= len; + middlePoints[0].y /= len; + // middlePoints[1].x /= len; + // middlePoints[1].y /= len; + + if (value.useMiddle) { + imagePromptsMulti.val[key] = [ + ...imagePromptsMulti.val[key], + ...middlePoints, + ]; + } else { + // Negative points + for (const [i, { start, end }] of value.indices.entries()) { + const startPoint = landmarks[start]; + // const endPoint = landmarks[end]; + + const startX = startPoint.x * imageSize.val.width; + const startY = startPoint.y * imageSize.val.height; + + // const endX = endPoint.x * imageSize.val.width; + // const endY = endPoint.y * imageSize.val.height; + + const middlePoint = middlePoints[0]; + const directionVector = { + x: middlePoint.x - startX, + y: middlePoint.y - startY, + }; + const directionVectorLength = Math.sqrt( + directionVector.x * directionVector.x + + directionVector.y * directionVector.y + ); + const negativePointDistance = + value.negativeOffset * directionVectorLength; + const negativePoint = { + x: + startX - + (negativePointDistance * directionVector.x) / directionVectorLength, + y: + startY - + (negativePointDistance * directionVector.y) / directionVectorLength, + label: 0, + }; + + const positivePointDistance = + value.positiveOffset * directionVectorLength; + positivePoints[i] = { + x: + positivePoints[i].x - + (positivePointDistance * directionVector.x) / directionVectorLength, + y: + positivePoints[i].y - + (positivePointDistance * directionVector.y) / directionVectorLength, + label: 1, + }; + + negativePoints.push(negativePoint); + } + imagePromptsMulti.val[key] = [ + ...imagePromptsMulti.val[key], + ...positivePoints, + ...negativePoints, + ]; + } + }); + console.log("Done"); +} + export function updateImagePrompts() { if (selectedLayer.val !== "" && selectedLayer.val !== undefined) { imagePromptsMulti.val = { @@ -248,10 +417,11 @@ export function LayerEditor() { id: "image-container", }, img({ + id: "image", class: "fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2", src: imageUrl, - onload: (e) => { + onload: async (e) => { imageSize.val = handleImageSize(e.target); document.getElementById("image-container").style.scale = @@ -265,6 +435,8 @@ export function LayerEditor() { const canvas = document.getElementById("mask-canvas"); canvas.width = e.target.naturalWidth; canvas.height = e.target.naturalHeight; + + await autoSegment(); }, oncontextmenu: async (e) => { e.preventDefault(); From 5663ff9e36da98e654d0d23dd3b95e090ebc605a Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 19:02:29 +0800 Subject: [PATCH 071/127] fix: auto segments not shown --- js/LayerEditor.js | 19 ++++++++----------- js/index.js | 11 ++++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 4eab270..f2e8730 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -42,13 +42,13 @@ const faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, { const layerMapping = { L_eye: { useMiddle: false, - positiveOffset: 0.1, + positiveOffset: 0, negativeOffset: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, }, R_eye: { useMiddle: false, - positiveOffset: 0.1, + positiveOffset: 0, negativeOffset: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, }, @@ -84,14 +84,12 @@ const layerMapping = { }, }; -async function drawPoints(part) {} - -async function autoSegment() { +export async function autoSegment() { const image = document.getElementById("image"); const landmarks = faceLandmarker.detect(image).faceLandmarks[0]; - Object.entries(layerMapping).forEach(([key, value]) => { - imagePromptsMulti.val[key] = []; - }); + // Object.entries(layerMapping).forEach(([key, value]) => { + // imagePromptsMulti.val[key] = []; + // }); Object.entries(layerMapping).forEach(([key, value]) => { const positivePoints = []; @@ -101,7 +99,7 @@ async function autoSegment() { // Positive points for (const { start, end } of value.indices) { const startPoint = landmarks[start]; - const endPoint = landmarks[end]; + // const endPoint = landmarks[end]; const startX = startPoint.x * imageSize.val.width; const startY = startPoint.y * imageSize.val.height; @@ -190,6 +188,7 @@ async function autoSegment() { ]; } }); + imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; console.log("Done"); } @@ -435,8 +434,6 @@ export function LayerEditor() { const canvas = document.getElementById("mask-canvas"); canvas.width = e.target.naturalWidth; canvas.height = e.target.naturalHeight; - - await autoSegment(); }, oncontextmenu: async (e) => { e.preventDefault(); diff --git a/js/index.js b/js/index.js index 2d5ff99..6955d7f 100644 --- a/js/index.js +++ b/js/index.js @@ -23,7 +23,7 @@ import { api } from "./api.js"; import { Container } from "./Container.js"; import { initModel, loadNpyTensor } from "./onnx.js"; import "https://code.iconify.design/3/3.1.0/iconify.min.js"; -import { drawSegment, getClicks } from "./LayerEditor.js"; +import { autoSegment, drawSegment, getClicks } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; export const generatedImages = {}; @@ -274,7 +274,11 @@ function getInputWidgetValue(node, inputIndex, widgetName) { * @param {LGraphNode} node */ function showMyImageEditor(node) { - let [isGeneratedImage, connectedImageFileName] = getInputWidgetValue(node, 0, "image"); + let [isGeneratedImage, connectedImageFileName] = getInputWidgetValue( + node, + 0, + "image" + ); if (!connectedImageFileName) { alertDialog.val = { text: "Please connect or generate an image first", @@ -338,8 +342,9 @@ function showMyImageEditor(node) { `${id}_${modelType}.npy` )}&type=output&subfolder=` ); - loadNpyTensor(embeedingUrl).then((tensor) => { + loadNpyTensor(embeedingUrl).then(async (tensor) => { embeddings.val = tensor; + await autoSegment(); drawSegment(getClicks()); }); targetNode.val = node; From 62344eb41de8d366cdbdeb2cdf44a4b147bba92a Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 19:16:13 +0800 Subject: [PATCH 072/127] chore: swap left right eyes definition --- js/LayerEditor.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index f2e8730..6b2225f 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -44,25 +44,25 @@ const layerMapping = { useMiddle: false, positiveOffset: 0, negativeOffset: 0.5, - indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, + indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, }, R_eye: { useMiddle: false, positiveOffset: 0, negativeOffset: 0.5, - indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, + indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, }, L_iris: { useMiddle: false, positiveOffset: 0, negativeOffset: 0.1, - indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, + indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, }, R_iris: { useMiddle: false, positiveOffset: 0, negativeOffset: 0.1, - indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, + indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, }, face: { useMiddle: false, @@ -87,9 +87,9 @@ const layerMapping = { export async function autoSegment() { const image = document.getElementById("image"); const landmarks = faceLandmarker.detect(image).faceLandmarks[0]; - // Object.entries(layerMapping).forEach(([key, value]) => { - // imagePromptsMulti.val[key] = []; - // }); + Object.entries(layerMapping).forEach(([key, value]) => { + imagePromptsMulti.val[key] = []; + }); Object.entries(layerMapping).forEach(([key, value]) => { const positivePoints = []; From bd3ed5ff0986cdee35a2c68c0f9a8734406fa1eb Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 19:46:52 +0800 Subject: [PATCH 073/127] feat: auto scale with offset and scale --- js/LayerEditor.js | 76 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 6b2225f..74a4cf3 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -42,44 +42,72 @@ const faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, { const layerMapping = { L_eye: { useMiddle: false, - positiveOffset: 0, - negativeOffset: 0.5, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, }, R_eye: { useMiddle: false, - positiveOffset: 0, - negativeOffset: 0.5, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, }, L_iris: { useMiddle: false, - positiveOffset: 0, - negativeOffset: 0.1, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, }, R_iris: { useMiddle: false, - positiveOffset: 0, - negativeOffset: 0.1, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, }, face: { useMiddle: false, - positiveOffset: -0.1, - negativeOffset: 0.1, + positiveOffsetX: 0, + positiveOffsetY: 40, + negativeOffsetX: 0, + negativeOffsetY: 60, + positiveScale: 0.2, + negativeScale: 0.6, indices: FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, }, mouth: { useMiddle: true, - positiveOffset: 0, - negativeOffset: 0.2, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LIPS, }, mouth_in: { useMiddle: true, - positiveOffset: 0, - negativeOffset: 0.2, + positiveOffsetX: 0, + positiveOffsetY: 0, + negativeOffsetX: 0, + negativeOffsetY: 0, + positiveScale: 0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LIPS, }, }; @@ -156,26 +184,34 @@ export async function autoSegment() { directionVector.y * directionVector.y ); const negativePointDistance = - value.negativeOffset * directionVectorLength; + value.negativeScale * directionVectorLength; const negativePoint = { x: startX - - (negativePointDistance * directionVector.x) / directionVectorLength, + (negativePointDistance * directionVector.x) / + directionVectorLength - + value.negativeOffsetX, y: startY - - (negativePointDistance * directionVector.y) / directionVectorLength, + (negativePointDistance * directionVector.y) / + directionVectorLength - + value.negativeOffsetY, label: 0, }; const positivePointDistance = - value.positiveOffset * directionVectorLength; + value.positiveScale * directionVectorLength; positivePoints[i] = { x: positivePoints[i].x - - (positivePointDistance * directionVector.x) / directionVectorLength, + (positivePointDistance * directionVector.x) / + directionVectorLength - + value.positiveOffsetX, y: positivePoints[i].y - - (positivePointDistance * directionVector.y) / directionVectorLength, + (positivePointDistance * directionVector.y) / + directionVectorLength - + value.positiveOffsetY, label: 1, }; From eb770eafeeeba1d6a9a0394592885172a78c04c1 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 16 Nov 2023 20:01:45 +0800 Subject: [PATCH 074/127] fix: loading disappeared earlier --- js/AvatarPreview.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 24836fd..fd8e65b 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -152,7 +152,11 @@ export function AvatarPreview() { }); api.addEventListener("executed", (evt) => { - loading.val = false; + const nodeId = evt.detail.node; + const targetNode = graph._nodes_by_id[nodeId]; + if (targetNode.type === "AvatarMainOutput") { + loading.val = false; + } }); const email = van.state(""); From 4c4536a9634544522320491e7d837c5f2e808791 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 08:51:46 -0500 Subject: [PATCH 075/127] fix: workflow --- js/AvatarPreview.js | 39 +++----- js/tw-styles.css | 218 +++++++++++++++----------------------------- 2 files changed, 87 insertions(+), 170 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index fd8e65b..0e4f332 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -19,12 +19,18 @@ import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; const workflowList = [ - "SilverWolfLora_withhand", - "SilverWolfLora", + "Auto_segment_workflow", + "BronyaZaychik_(ChinaDress)", + "BronyaZaychik_(Default_Silverwing)", + "BronyaZaychik_(Non-official_office_ladysuit)", + "BronyaZaychik_(Official_office_ladysuit)", "BronyaZaychikLora_withhand", - "BronyaZaychikLora", + "SilverWolf_(Default)", + "SilverWolf_(Maid)", + "SilverWolf_(Maid)", ]; +// const workflowList = ["Auto_segment_workflow"]; async function loadJSONWorkflow(name) { const json = await (await fetch(`./get_workflow?name=${name}`)).json(); app.loadGraphData(json); @@ -185,29 +191,6 @@ export function AvatarPreview() { }, "Get your DALLE3 AI Personal Clone", ), - // input({ - // type: "file", - // class: "file-input file-input-bordered w-full", - // }), - - // label( - // { - // class: () => - // " aspect-square w-full ring-1 ring-black bg-[#f6f6f6] rounded-lg flex justify-center items-center text-black cursor-pointer mt-24", - // // for: "imgtosam", - // onclick: () => {}, - // }, - // div( - // { - // class: () => " absolute flex justify-center items-center ", - // }, - // "upload your image" - // ), - // img({ - // class: () => " z-[10] object-contain w-[550px] h-[394px]", - // id: "imgUrl", - // }) - // ), div( { class: () => @@ -583,7 +566,7 @@ export function AvatarPreview() { class: () => "p-4 btn btn-ghost items-start", onclick: async (e) => { e.preventDefault(); - await loadJSONWorkflow(""); + await loadJSONWorkflow(val); await new Promise((resolve) => setTimeout(resolve, 200)); const kSampler = app.graph.findNodesByType("KSampler")[0]; if (!kSampler) isGenerateFlow.val = false; @@ -658,7 +641,7 @@ export function AvatarPreview() { return button( { class: () => - "absolute top-4 right-4 btn sm:w-10 w-32 text-black btn-ghost text-xs !px-0 normal-case sm:btn-md btn-sm", + "absolute top-4 right-4 btn sm:w-32 w-20 text-black btn-ghost text-xs z-[200] !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, "Twitter", diff --git a/js/tw-styles.css b/js/tw-styles.css index fb745ab..615e3e7 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -730,12 +730,6 @@ html{ .tab:hover{ --tw-text-opacity: 1; } - - .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover, - .tabs-boxed :is(input:checked):hover{ - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); - } } .btn{ @@ -2180,25 +2174,10 @@ details.collapse summary::-webkit-details-marker{ background-position: top right; } -.tabs-boxed{ - border-radius: var(--rounded-btn, 0.5rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - padding: 0.25rem; -} - .tabs-boxed .tab{ border-radius: var(--rounded-btn, 0.5rem); } -.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]), - .tabs-boxed :is(input:checked){ - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2578,6 +2557,10 @@ details.collapse summary::-webkit-details-marker{ left: 1rem; } +.left-52{ + left: 13rem; +} + .right-0{ right: 0px; } @@ -2606,10 +2589,6 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } -.left-52{ - left: 13rem; -} - .z-\[1000\]{ z-index: 1000; } @@ -2634,14 +2613,6 @@ details.collapse summary::-webkit-details-marker{ z-index: 99; } -.z-\[2000\]{ - z-index: 2000; -} - -.z-\[3000\]{ - z-index: 3000; -} - .m-0{ margin: 0px; } @@ -2664,6 +2635,14 @@ details.collapse summary::-webkit-details-marker{ margin-left: 0.5rem; } +.ml-4{ + margin-left: 1rem; +} + +.mr-4{ + margin-right: 1rem; +} + .mt-2{ margin-top: 0.5rem; } @@ -2680,14 +2659,6 @@ details.collapse summary::-webkit-details-marker{ margin-top: 2rem; } -.ml-4{ - margin-left: 1rem; -} - -.mr-4{ - margin-right: 1rem; -} - .block{ display: block; } @@ -2696,10 +2667,6 @@ details.collapse summary::-webkit-details-marker{ display: flex; } -.\!flex{ - display: flex !important; -} - .hidden{ display: none; } @@ -2712,6 +2679,14 @@ details.collapse summary::-webkit-details-marker{ height: 0.75rem; } +.h-36{ + height: 9rem; +} + +.h-96{ + height: 24rem; +} + .h-\[394px\]{ height: 394px; } @@ -2725,48 +2700,32 @@ details.collapse summary::-webkit-details-marker{ height: 100%; } -.h-\[400px\]{ - height: 400px; -} - -.h-24{ - height: 6rem; -} - -.h-36{ - height: 9rem; -} - -.h-96{ - height: 24rem; -} - -.min-h-\[600px\]{ - min-height: 600px; -} - .min-h-\[400px\]{ min-height: 400px; } -.w-24{ - width: 6rem; -} - .w-3{ width: 0.75rem; } +.w-32{ + width: 8rem; +} + +.w-40{ + width: 10rem; +} + .w-56{ width: 14rem; } -.w-96{ +.w-\[24rem\]{ width: 24rem; } -.w-\[24rem\]{ - width: 24rem; +.w-\[32rem\]{ + width: 32rem; } .w-\[550px\]{ @@ -2782,34 +2741,30 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-\[32rem\]{ - width: 32rem; +.w-10{ + width: 2.5rem; } -.w-32{ - width: 8rem; +.w-14{ + width: 3.5rem; } -.w-\[400px\]{ - width: 400px; +.w-24{ + width: 6rem; } -.w-40{ - width: 10rem; -} - -.w-36{ - width: 9rem; -} - -.w-52{ - width: 13rem; +.w-20{ + width: 5rem; } .w-64{ width: 16rem; } +.w-96{ + width: 24rem; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2856,14 +2811,6 @@ details.collapse summary::-webkit-details-marker{ align-items: center; } -.justify-start{ - justify-content: flex-start; -} - -.\!justify-start{ - justify-content: flex-start !important; -} - .justify-center{ justify-content: center; } @@ -2884,10 +2831,6 @@ details.collapse summary::-webkit-details-marker{ gap: 1rem; } -.\!gap-0{ - gap: 0px !important; -} - .place-self-end{ place-self: end; } @@ -2928,6 +2871,16 @@ details.collapse summary::-webkit-details-marker{ border-radius: 0.125rem; } +.rounded-b-md{ + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-b-none{ + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + .rounded-l-md{ border-top-left-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -2948,16 +2901,6 @@ details.collapse summary::-webkit-details-marker{ border-bottom-right-radius: 0px; } -.rounded-b-md{ - border-bottom-right-radius: 0.375rem; - border-bottom-left-radius: 0.375rem; -} - -.rounded-b-none{ - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; -} - .rounded-t-md{ border-top-left-radius: 0.375rem; border-top-right-radius: 0.375rem; @@ -2971,14 +2914,14 @@ details.collapse summary::-webkit-details-marker{ border-width: 2px; } -.border-t-4{ - border-top-width: 4px; -} - .border-b-0{ border-bottom-width: 0px; } +.border-t-4{ + border-top-width: 4px; +} + .border-dashed{ border-style: dashed; } @@ -2987,26 +2930,26 @@ details.collapse summary::-webkit-details-marker{ border-style: none; } -.border-orange-500{ - --tw-border-opacity: 1; - border-color: rgb(249 115 22 / var(--tw-border-opacity)); -} - .border-black{ --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); } -.border-purple-500{ - --tw-border-opacity: 1; - border-color: rgb(168 85 247 / var(--tw-border-opacity)); -} - .border-gray-300{ --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-orange-500{ + --tw-border-opacity: 1; + border-color: rgb(249 115 22 / var(--tw-border-opacity)); +} + +.border-purple-500{ + --tw-border-opacity: 1; + border-color: rgb(168 85 247 / var(--tw-border-opacity)); +} + .bg-\[\#f6f6f6\]{ --tw-bg-opacity: 1; background-color: rgb(246 246 246 / var(--tw-bg-opacity)); @@ -3103,10 +3046,6 @@ details.collapse summary::-webkit-details-marker{ padding: 0px; } -.p-10{ - padding: 2.5rem; -} - .p-2{ padding: 0.5rem; } @@ -3144,19 +3083,10 @@ details.collapse summary::-webkit-details-marker{ padding-bottom: 0.5rem; } -.py-4{ - padding-top: 1rem; - padding-bottom: 1rem; -} - .text-start{ text-align: start; } -.\!text-start{ - text-align: start !important; -} - .font-gabarito{ font-family: Gabarito; } @@ -3223,6 +3153,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(194 65 12 / var(--tw-text-opacity)); } +.text-purple-500{ + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + .text-transparent{ color: transparent; } @@ -3232,11 +3167,6 @@ details.collapse summary::-webkit-details-marker{ color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-purple-500{ - --tw-text-opacity: 1; - color: rgb(168 85 247 / var(--tw-text-opacity)); -} - .opacity-80{ opacity: 0.8; } @@ -3476,6 +3406,10 @@ img[src] { .sm\:w-10{ width: 2.5rem; } + + .sm\:w-32{ + width: 8rem; + } } @media (min-width: 768px){ From a401da9dd23b28d20251199cac83f5fe9fb58ad9 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 23:36:38 +0800 Subject: [PATCH 076/127] fix: drop down menu --- js/AvatarPreview.js | 5 +- js/tw-styles.css | 2026 ++++++++++++++++++------------------------- 2 files changed, 832 insertions(+), 1199 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 0e4f332..ed0c891 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -27,7 +27,6 @@ const workflowList = [ "BronyaZaychikLora_withhand", "SilverWolf_(Default)", "SilverWolf_(Maid)", - "SilverWolf_(Maid)", ]; // const workflowList = ["Auto_segment_workflow"]; @@ -537,7 +536,7 @@ export function AvatarPreview() { return div( { class: () => - "dropdown dropdown-hover left-0 top-0 z-[200] pointer-events-auto text-black ", + "dropdown dropdown-hover dropdown-bottom z-[200] pointer-events-auto text-black ", }, label( { @@ -557,7 +556,7 @@ export function AvatarPreview() { ul( { class: () => - "dropdown-content z-[200] menu p-2 shadow rounded-box w-64 bg-white", + "dropdown-content -left-[100px] z-[200] menu p-2 shadow rounded-box w-96 bg-white", tabIndex: () => 0, }, workflowList.map((val, index) => { diff --git a/js/tw-styles.css b/js/tw-styles.css index 615e3e7..85884e3 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com */ /* @@ -640,6 +640,18 @@ html{ justify-items: center; gap: 1rem; text-align: center; +} + +@media (min-width: 640px){ + .alert{ + grid-auto-flow: column; + grid-template-columns: auto minmax(auto,1fr); + justify-items: start; + text-align: start; + } +} + +.alert{ border-radius: var(--rounded-box, 1rem); border-width: 1px; --tw-border-opacity: 1; @@ -652,15 +664,6 @@ html{ background-color: var(--alert-bg); } -@media (min-width: 640px){ - .alert{ - grid-auto-flow: column; - grid-template-columns: auto minmax(auto,1fr); - justify-items: start; - text-align: start; - } -} - .avatar{ position: relative; display: inline-flex; @@ -693,8 +696,8 @@ html{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); height: 1.25rem; font-size: 0.875rem; line-height: 1.25rem; @@ -712,24 +715,29 @@ html{ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } -@media (hover:hover){ - .label a:hover{ - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - } +.badge-neutral{ + --tw-border-opacity: 1; + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} - .menu li > *:not(ul):not(.menu-title):not(details):active, -.menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active{ - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - } +.badge-outline.badge-neutral{ + --tw-text-opacity: 1; + color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); +} - .tab:hover{ - --tw-text-opacity: 1; - } +.btm-nav > *:where(.active){ + border-top-width: 2px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.btm-nav > * .label{ + font-size: 1rem; + line-height: 1.5rem; } .btn{ @@ -746,27 +754,11 @@ html{ justify-content: center; border-radius: var(--rounded-btn, 0.5rem); border-color: transparent; - border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)); padding-left: 1rem; padding-right: 1rem; text-align: center; font-size: 0.875rem; line-height: 1em; - gap: 0.5rem; - font-weight: 600; - text-decoration-line: none; - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - border-width: var(--border-btn, 1px); - animation: button-pop var(--animation-btn, 0.25s) ease-out; - transition-property: color, background-color, border-color, opacity, box-shadow, transform; - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); - background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)); } .btn-disabled, @@ -782,6 +774,161 @@ html{ padding: 0px; } +.btn{ + gap: 0.5rem; + font-weight: 600; + text-decoration-line: none; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; + transition-property: color, background-color, border-color, opacity, box-shadow, transform; +} + +.btn:active:hover, + .btn:active:focus{ + animation: button-pop 0s ease-out; + transform: scale(var(--btn-focus-scale, 0.97)); +} + +.btn{ + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + text-decoration-line: none; + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)); + border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)); +} + +@supports not (color: oklch(0 0 0)){ + .btn{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } +} + +@media (hover: hover){ + .btn:hover{ + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, + black + ); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, + black + ); + } + + @supports not (color: oklch(0 0 0)){ + .btn:hover{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + } +} + +.btn:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +.btn-neutral{ + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + outline-color: var(--fallback-n,oklch(var(--n)/1)); +} + +@supports (color: oklch(0 0 0)){ + .btn-neutral{ + --btn-color: var(--n); + } +} + +@supports not (color: oklch(0 0 0)){ + .btn-neutral{ + --btn-color: var(--fallback-n); + } +} + +.btn.glass{ + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +@media (hover: hover){ + .btn.glass:hover{ + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } +} + +.btn.glass.btn-active{ + --glass-opacity: 25%; + --glass-border-opacity: 15%; +} + +.btn-ghost{ + border-width: 1px; + border-color: transparent; + background-color: transparent; + color: currentColor; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +@media (hover: hover){ + .btn-ghost:hover{ + border-color: transparent; + } + + @supports (color: oklch(0 0 0)){ + .btn-ghost:hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + } + } +} + +.btn-ghost.btn-active{ + border-color: transparent; + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.btn.btn-disabled, + .btn[disabled], + .btn:disabled{ + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +@media (hover: hover){ + .btn-disabled:hover, + .btn[disabled]:hover, + .btn:disabled:hover{ + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } +} + :where(.btn:is(input[type="checkbox"])), :where(.btn:is(input[type="radio"])){ width: auto; @@ -861,10 +1008,6 @@ html{ flex-direction: row; align-items: center; align-self: stretch; - margin-top: 1rem; - margin-bottom: 1rem; - height: 1rem; - white-space: nowrap; } .divider:before, @@ -874,8 +1017,18 @@ html{ flex-grow: 1; --tw-content: ''; content: var(--tw-content); - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); +} + +.divider{ + margin-top: 1rem; + margin-bottom: 1rem; + height: 1rem; + white-space: nowrap; +} + +.divider:not(:empty){ + gap: 1rem; } .dropdown{ @@ -903,8 +1056,8 @@ html{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); } .dropdown-end .dropdown-content{ @@ -958,150 +1111,16 @@ html{ .dropdown.dropdown-hover:hover .dropdown-content{ visibility: visible; opacity: 1; - } - - .btn:hover{ - --tw-border-opacity: 1; - border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); - border-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, - black - ); - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); - background-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, - black - ); - } - - @supports not (color: oklch(0 0 0)){ - .btn:hover{ - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } - } - - .btn.glass:hover{ - --glass-opacity: 25%; - --glass-border-opacity: 15%; - } - - .btn-ghost:hover{ - border-color: transparent; - } - - @supports (color: oklch(0 0 0)){ - .btn-ghost:hover{ - background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); - } - } - - .btn-disabled:hover, - .btn[disabled]:hover, - .btn:disabled:hover{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; - } - - .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ - background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - } - - .dropdown.dropdown-hover:hover .dropdown-content{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } - - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ - cursor: pointer; - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - outline: 2px solid transparent; - outline-offset: 2px; - } - - @supports (color: oklch(0 0 0)){ - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - } - } - - .tab[disabled], - .tab[disabled]:hover{ - cursor: not-allowed; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; - } } .dropdown:is(details) summary::-webkit-details-marker{ display: none; } -.file-input{ - height: 3rem; - flex-shrink: 1; - -webkit-padding-end: 1rem; - padding-inline-end: 1rem; - font-size: 1rem; - line-height: 2; - line-height: 1.5rem; - overflow: hidden; - border-radius: var(--rounded-btn, 0.5rem); - border-width: 1px; - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.file-input::file-selector-button{ - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; - display: inline-flex; - height: 100%; - flex-shrink: 0; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - padding-left: 1rem; - padding-right: 1rem; - text-align: center; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 1em; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 200ms; - border-style: solid; - --tw-border-opacity: 1; - border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - font-weight: 600; - text-transform: uppercase; - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - text-decoration-line: none; - border-width: var(--border-btn, 1px); - animation: button-pop var(--animation-btn, 0.25s) ease-out; -} - .label{ display: flex; -webkit-user-select: none; @@ -1115,615 +1134,28 @@ html{ padding-bottom: 0.5rem; } +@media(hover:hover){ + .label a:hover{ + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + } +} + .input{ flex-shrink: 1; height: 3rem; padding-left: 1rem; padding-right: 1rem; - font-size: 1rem; - line-height: 2; - line-height: 1.5rem; - border-radius: var(--rounded-btn, 0.5rem); - border-width: 1px; - border-color: transparent; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.join{ - display: inline-flex; - align-items: stretch; - border-radius: var(--rounded-btn, 0.5rem); -} - -.join :where(.join-item){ - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:not(:first-child):not(:last-child), - .join *:not(:first-child):not(:last-child) .join-item{ - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .join-item{ - border-start-end-radius: 0; - border-end-end-radius: 0; -} - -.join .dropdown .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .dropdown .join-item{ - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -.join :where(.join-item:first-child:not(:last-child)), - .join :where(*:first-child:not(:last-child) .join-item){ - border-end-start-radius: inherit; - border-start-start-radius: inherit; -} - -.join .join-item:last-child:not(:first-child), - .join *:last-child:not(:first-child) .join-item{ - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join :where(.join-item:last-child:not(:first-child)), - .join :where(*:last-child:not(:first-child) .join-item){ - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -@supports not selector(:has(*)){ - :where(.join *){ - border-radius: inherit; - } -} - -@supports selector(:has(*)){ - :where(.join *:has(.join-item)){ - border-radius: inherit; - } -} - -.link{ - cursor: pointer; - text-decoration-line: underline; -} - -.mask{ - -webkit-mask-size: contain; - mask-size: contain; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-position: center; - mask-position: center; -} - -.menu{ - display: flex; - flex-direction: column; - flex-wrap: wrap; - font-size: 0.875rem; - line-height: 1.25rem; - padding: 0.5rem; -} - -.menu :where(li ul){ - position: relative; - white-space: nowrap; - -webkit-margin-start: 1rem; - margin-inline-start: 1rem; - -webkit-padding-start: 0.5rem; - padding-inline-start: 0.5rem; -} - -.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), - .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ - display: grid; - grid-auto-flow: column; - align-content: flex-start; - align-items: center; - gap: 0.5rem; - grid-auto-columns: minmax(auto, max-content) auto max-content; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.menu li.disabled{ - cursor: not-allowed; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - color: var(--fallback-bc,oklch(var(--bc)/0.3)); -} - -.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ - display: none; -} - -:where(.menu li){ - position: relative; - display: flex; - flex-shrink: 0; - flex-direction: column; - flex-wrap: wrap; - align-items: stretch; -} - -:where(.menu li) .badge{ - justify-self: end; -} - -.modal{ - pointer-events: none; - position: fixed; - inset: 0px; - margin: 0px; - display: grid; - height: 100%; - max-height: none; - width: 100%; - max-width: none; - justify-items: center; - padding: 0px; - opacity: 0; - overscroll-behavior: contain; - z-index: 999; - background-color: transparent; - color: inherit; - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-property: transform, opacity, visibility; - overflow-y: hidden; -} - -:where(.modal){ - align-items: center; -} - -.modal-box{ - max-height: calc(100vh - 5em); - grid-column-start: 1; - grid-row-start: 1; - width: 91.666667%; - max-width: 32rem; - --tw-scale-x: .9; - --tw-scale-y: .9; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); - border-bottom-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); - border-top-left-radius: var(--rounded-box, 1rem); - border-top-right-radius: var(--rounded-box, 1rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); - padding: 1.5rem; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 200ms; - box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; - overflow-y: auto; - overscroll-behavior: contain; -} - -.modal-open, -.modal:target, -.modal-toggle:checked + .modal, -.modal[open]{ - pointer-events: auto; - visibility: visible; - opacity: 1; -} - -.modal-action{ - display: flex; - margin-top: 1.5rem; - justify-content: flex-end; -} - -:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])){ - overflow: hidden; -} - -.radio{ - flex-shrink: 0; - --chkbg: var(--bc); - height: 1.5rem; - width: 1.5rem; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border-radius: 9999px; - border-width: 1px; - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0.2; -} - -.tabs{ - display: grid; - align-items: flex-end; -} - -.tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(.tab-active), - .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(.tab-active){ - border-bottom-color: transparent; -} - -.tab{ - position: relative; - grid-row-start: 1; - display: inline-flex; - height: 2rem; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - text-align: center; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - --tab-padding: 1rem; - --tw-text-opacity: 0.5; - --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); - --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); - --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); - color: var(--tab-color); - -webkit-padding-start: var(--tab-padding, 1rem); - padding-inline-start: var(--tab-padding, 1rem); - -webkit-padding-end: var(--tab-padding, 1rem); - padding-inline-end: var(--tab-padding, 1rem); -} - -.tab:is(input[type="radio"]):after{ - --tw-content: attr(aria-label); - content: var(--tw-content); -} - -.tab:not(input):empty{ - cursor: default; - grid-column-start: span 9999; -} - -.tab-content{ - grid-column: 1 / -1; - grid-column-end: span 9999; - grid-row-start: 2; - margin-top: calc(var(--tab-border) * -1); - display: none; - border-color: transparent; - border-width: var(--tab-border, 0); -} - -:checked + .tab-content:nth-child(2), - .tab-active + .tab-content:nth-child(2){ - border-start-start-radius: 0px; -} - -input.tab:checked + .tab-content, -.tab-active + .tab-content{ - display: block; -} - -.textarea{ - min-height: 3rem; - flex-shrink: 1; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - font-size: 0.875rem; - line-height: 1.25rem; line-height: 2; border-radius: var(--rounded-btn, 0.5rem); border-width: 1px; border-color: transparent; --tw-bg-opacity: 1; background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.avatar-group :where(.avatar){ - overflow: hidden; - border-radius: 9999px; - border-width: 4px; - --tw-border-opacity: 1; - border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); -} - -.badge-neutral{ - --tw-border-opacity: 1; - border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); -} - -.badge-outline.badge-neutral{ - --tw-text-opacity: 1; - color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); -} - -.btm-nav > *:where(.active){ - border-top-width: 2px; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; } -.btn:active:hover, - .btn:active:focus{ - animation: button-pop 0s ease-out; - transform: scale(var(--btn-focus-scale, 0.97)); -} - -@supports not (color: oklch(0 0 0)){ - .btn{ - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } - - .btn-neutral{ - --btn-color: var(--fallback-n); - } -} - -.btn:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; -} - -@supports (color: oklch(0 0 0)){ - .btn-neutral{ - --btn-color: var(--n); - } -} - -.btn-neutral{ - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - outline-color: var(--fallback-n,oklch(var(--n)/1)); -} - -.btn.glass{ - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: currentColor; -} - -.btn.glass.btn-active{ - --glass-opacity: 25%; - --glass-border-opacity: 15%; -} - -.btn-ghost{ - border-width: 1px; - border-color: transparent; - background-color: transparent; - color: currentColor; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: currentColor; -} - -.btn-ghost.btn-active{ - border-color: transparent; - background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.btn.btn-disabled, - .btn[disabled], - .btn:disabled{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - -.btn:is(input[type="checkbox"]:checked), -.btn:is(input[type="radio"]:checked){ - --tw-border-opacity: 1; - border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - -.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ - outline-color: var(--fallback-p,oklch(var(--p)/1)); -} - -@keyframes button-pop{ - 0%{ - transform: scale(var(--btn-focus-scale, 0.98)); - } - - 40%{ - transform: scale(1.02); - } - - 100%{ - transform: scale(1); - } -} - -@keyframes checkmark{ - 0%{ - background-position-y: 5px; - } - - 50%{ - background-position-y: -2px; - } - - 100%{ - background-position-y: 0; - } -} - -details.collapse{ - width: 100%; -} - -details.collapse summary{ - position: relative; - display: block; - outline: 2px solid transparent; - outline-offset: 2px; -} - -details.collapse summary::-webkit-details-marker{ - display: none; -} - -.collapse:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -.collapse:has(.collapse-title:focus-visible), -.collapse:has(> input[type="checkbox"]:focus-visible), -.collapse:has(> input[type="radio"]:focus-visible){ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], -.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), -.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title{ - cursor: pointer; -} - -.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title{ - cursor: unset; -} - -:where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]){ - z-index: 1; -} - -.collapse-title, -:where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]){ - width: 100%; - padding: 1rem; - -webkit-padding-end: 3rem; - padding-inline-end: 3rem; - min-height: 3.75rem; - transition: background-color 0.2s ease-out; -} - -.collapse[open] > :where(.collapse-content), -.collapse-open > :where(.collapse-content), -.collapse:focus:not(.collapse-close) > :where(.collapse-content), -.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), -.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content){ - padding-bottom: 1rem; - transition: padding 0.2s ease-out, - background-color 0.2s ease-out; -} - -.collapse[open].collapse-arrow > .collapse-title:after, -.collapse-open.collapse-arrow > .collapse-title:after, -.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, -.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ - --tw-translate-y: -50%; - --tw-rotate: 225deg; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.collapse[open].collapse-plus > .collapse-title:after, -.collapse-open.collapse-plus > .collapse-title:after, -.collapse-plus:focus:not(.collapse-close) > .collapse-title:after, -.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ - content: "−"; -} - -.divider:not(:empty){ - gap: 1rem; -} - -.dropdown.dropdown-open .dropdown-content, -.dropdown:focus .dropdown-content, -.dropdown:focus-within .dropdown-content{ - --tw-scale-x: 1; - --tw-scale-y: 1; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.file-input-bordered{ - --tw-border-opacity: 0.2; -} - -.file-input:focus{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.file-input-disabled, - .file-input[disabled]{ - cursor: not-allowed; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - --tw-text-opacity: 0.2; -} - -.file-input-disabled::-moz-placeholder, .file-input[disabled]::-moz-placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.file-input-disabled::placeholder, - .file-input[disabled]::placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.file-input-disabled::file-selector-button, .file-input[disabled]::file-selector-button{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - .input input:focus{ outline: 2px solid transparent; outline-offset: 2px; @@ -1770,11 +1202,81 @@ details.collapse summary::-webkit-details-marker{ --tw-placeholder-opacity: 0.2; } +.join{ + display: inline-flex; + align-items: stretch; +} + +.join :where(.join-item){ + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item{ + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item{ + border-start-end-radius: 0; + border-end-end-radius: 0; +} + +.join .dropdown .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .dropdown .join-item{ + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item){ + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item{ + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item){ + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +.join{ + border-radius: var(--rounded-btn, 0.5rem); +} + .join > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - -webkit-margin-start: -1px; - margin-inline-start: -1px; + margin-inline-start: -1px; +} + +@supports not selector(:has(*)){ + :where(.join *){ + border-radius: inherit; + } +} + +@supports selector(:has(*)){ + :where(.join *:has(.join-item)){ + border-radius: inherit; + } +} + +.link{ + cursor: pointer; + text-decoration-line: underline; } .link:focus{ @@ -1787,122 +1289,67 @@ details.collapse summary::-webkit-details-marker{ outline-offset: 2px; } -.loading{ - pointer-events: none; - display: inline-block; - aspect-ratio: 1 / 1; - width: 1.5rem; - background-color: currentColor; - -webkit-mask-size: 100%; - mask-size: 100%; +.mask{ + -webkit-mask-size: contain; + mask-size: contain; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; - -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.loading-spinner{ - -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +.menu{ + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-size: 0.875rem; + line-height: 1.25rem; } -.loading-md{ - width: 1.5rem; -} - -.loading-lg{ - width: 2.5rem; -} - -:where(.menu li:empty){ - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - margin: 0.5rem 1rem; - height: 1px; -} - -.menu :where(li ul):before{ - position: absolute; - bottom: 0.75rem; - inset-inline-start: 0px; - top: 0.75rem; - width: 1px; - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - content: ""; +.menu :where(li ul){ + position: relative; + white-space: nowrap; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), -.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ - border-radius: var(--rounded-btn, 0.5rem); - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - text-align: start; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 200ms; - text-wrap: balance; + .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: 0.5rem; + grid-auto-columns: minmax(auto, max-content) auto max-content; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } -:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ - cursor: pointer; - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - outline: 2px solid transparent; - outline-offset: 2px; +.menu li.disabled{ + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } -.menu li > *:not(ul):not(.menu-title):not(details):active, -.menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active{ - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); -} - -.menu :where(li > details > summary)::-webkit-details-marker{ +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ display: none; } -.menu :where(li > details > summary):after, -.menu :where(li > .menu-dropdown-toggle):after{ +.menu{ + padding: 0.5rem; +} + +:where(.menu li){ + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; +} + +:where(.menu li) .badge{ justify-self: end; - display: block; - margin-top: -0.5rem; - height: 0.5rem; - width: 0.5rem; - transform: rotate(45deg); - transition-property: transform, margin-top; - transition-duration: 0.3s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - content: ""; - transform-origin: 75% 75%; - box-shadow: 2px 2px; - pointer-events: none; -} - -.menu :where(li > details[open] > summary):after, -.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after{ - transform: rotate(225deg); - margin-top: 0; -} - -.mockup-phone .display{ - overflow: hidden; - border-radius: 40px; - margin-top: -25px; } .mockup-browser .mockup-browser-toolbar .input{ @@ -1951,12 +1398,464 @@ details.collapse summary::-webkit-details-marker{ opacity: 0.6; } +.mockup-phone .display{ + overflow: hidden; + border-radius: 40px; + margin-top: -25px; +} + +.modal{ + pointer-events: none; + position: fixed; + inset: 0px; + margin: 0px; + display: grid; + height: 100%; + max-height: none; + width: 100%; + max-width: none; + justify-items: center; + padding: 0px; + opacity: 0; + overscroll-behavior: contain; + z-index: 999; + background-color: transparent; + color: inherit; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity, visibility; + overflow-y: hidden; +} + .modal:not(dialog:not(.modal-open)), .modal::backdrop{ background-color: rgba(0, 0, 0, 0.3); animation: modal-pop 0.2s ease-out; } +:where(.modal){ + align-items: center; +} + +.modal-box{ + max-height: calc(100vh - 5em); + grid-column-start: 1; + grid-row-start: 1; + width: 91.666667%; + max-width: 32rem; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + padding: 1.5rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; + overflow-y: auto; + overscroll-behavior: contain; +} + +.modal-open, +.modal:target, +.modal-toggle:checked + .modal, +.modal[open]{ + pointer-events: auto; + visibility: visible; + opacity: 1; +} + +.modal-action{ + display: flex; + margin-top: 1.5rem; + justify-content: flex-end; +} + +.modal-action > :not([hidden]) ~ :not([hidden]){ + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])){ + overflow: hidden; +} + +.table tr.active, + .table tr.active:nth-child(even), + .table-zebra tbody tr:nth-child(even){ + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.table-zebra tr.active, + .table-zebra tr.active:nth-child(even), + .table-zebra-zebra tbody tr:nth-child(even){ + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); +} + +.textarea{ + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.textarea-bordered{ + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea:focus{ + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[disabled]{ + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.avatar-group :where(.avatar){ + overflow: hidden; + border-radius: 9999px; + border-width: 4px; + --tw-border-opacity: 1; + border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); +} + +.btn:is(input[type="checkbox"]:checked), +.btn:is(input[type="radio"]:checked){ + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +@media (hover: hover){ + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } +} + +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@keyframes button-pop{ + 0%{ + transform: scale(var(--btn-focus-scale, 0.98)); + } + + 40%{ + transform: scale(1.02); + } + + 100%{ + transform: scale(1); + } +} + +@keyframes checkmark{ + 0%{ + background-position-y: 5px; + } + + 50%{ + background-position-y: -2px; + } + + 100%{ + background-position-y: 0; + } +} + +details.collapse{ + width: 100%; +} + +details.collapse summary{ + position: relative; + display: block; +} + +details.collapse summary::-webkit-details-marker{ + display: none; +} + +.collapse:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +details.collapse summary{ + outline: 2px solid transparent; + outline-offset: 2px; +} + +.collapse:has(.collapse-title:focus-visible), +.collapse:has(> input[type="checkbox"]:focus-visible), +.collapse:has(> input[type="radio"]:focus-visible){ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], +.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), +.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title{ + cursor: pointer; +} + +.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title{ + cursor: unset; +} + +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]){ + z-index: 1; +} + +.collapse-title, +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]){ + width: 100%; + padding: 1rem; + padding-inline-end: 3rem; + min-height: 3.75rem; + transition: background-color 0.2s ease-out; +} + +.collapse[open] > :where(.collapse-content), +.collapse-open > :where(.collapse-content), +.collapse:focus:not(.collapse-close) > :where(.collapse-content), +.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), +.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content){ + padding-bottom: 1rem; + transition: padding 0.2s ease-out, + background-color 0.2s ease-out; +} + +.collapse[open].collapse-arrow > .collapse-title:after, +.collapse-open.collapse-arrow > .collapse-title:after, +.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ + --tw-translate-y: -50%; + --tw-rotate: 225deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.collapse[open].collapse-plus > .collapse-title:after, +.collapse-open.collapse-plus > .collapse-title:after, +.collapse-plus:focus:not(.collapse-close) > .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ + content: "−"; +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:focus .dropdown-content, +.dropdown:focus-within .dropdown-content{ + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.loading{ + pointer-events: none; + display: inline-block; + aspect-ratio: 1 / 1; + width: 1.5rem; + background-color: currentColor; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-spinner{ + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-md{ + width: 1.5rem; +} + +.loading-lg{ + width: 2.5rem; +} + +:where(.menu li:empty){ + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + margin: 0.5rem 1rem; + height: 1px; +} + +.menu :where(li ul){ + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; +} + +.menu :where(li ul):before{ + position: absolute; + bottom: 0.75rem; + inset-inline-start: 0px; + top: 0.75rem; + width: 1px; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + content: ""; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: start; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + text-wrap: balance; +} + +:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ + cursor: pointer; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; +} + +@media (hover: hover){ + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ + cursor: pointer; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; + } + + @supports (color: oklch(0 0 0)){ + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + } + } +} + +.menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active{ + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +@media(hover:hover){ + .menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active{ + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + } +} + +.menu li.disabled{ + color: var(--fallback-bc,oklch(var(--bc)/0.3)); +} + +.menu :where(li > details > summary)::-webkit-details-marker{ + display: none; +} + +.menu :where(li > details > summary):after, +.menu :where(li > .menu-dropdown-toggle):after{ + justify-self: end; + display: block; + margin-top: -0.5rem; + height: 0.5rem; + width: 0.5rem; + transform: rotate(45deg); + transition-property: transform, margin-top; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.menu :where(li > details[open] > summary):after, +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after{ + transform: rotate(225deg); + margin-top: 0; +} + .modal-open .modal-box, .modal-toggle:checked + .modal .modal-box, .modal:target .modal-box, @@ -1967,12 +1866,6 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.modal-action > :not([hidden]) ~ :not([hidden]){ - --tw-space-x-reverse: 0; - margin-right: calc(0.5rem * var(--tw-space-x-reverse)); - margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); -} - @keyframes modal-pop{ 0%{ opacity: 0; @@ -1985,32 +1878,6 @@ details.collapse summary::-webkit-details-marker{ } } -.radio:focus{ - box-shadow: none; -} - -.radio:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -.radio:checked, - .radio[aria-checked="true"]{ - --tw-bg-opacity: 1; - background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); - background-image: none; - animation: radiomark var(--animation-input, 0.2s) ease-out; - box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, - 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; -} - -.radio:disabled{ - cursor: not-allowed; - opacity: 0.2; -} - @keyframes radiomark{ 0%{ box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, @@ -2052,182 +1919,6 @@ details.collapse summary::-webkit-details-marker{ } } -.tabs-lifted .tab:focus-visible{ - border-end-end-radius: 0; - border-end-start-radius: 0; -} - -.tab.tab-active:not(.tab-disabled):not([disabled]), - .tab:is(input:checked){ - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 1; - --tw-text-opacity: 1; -} - -.tab:focus{ - outline: 2px solid transparent; - outline-offset: 2px; -} - -.tab:focus-visible{ - outline: 2px solid currentColor; - outline-offset: -5px; -} - -.tab-disabled, - .tab[disabled]{ - cursor: not-allowed; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - -.tabs-bordered .tab{ - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0.2; - border-style: solid; - border-bottom-width: calc(var(--tab-border, 1px) + 1px); -} - -.tabs-lifted .tab{ - border: var(--tab-border, 1px) solid transparent; - border-width: 0 0 var(--tab-border, 1px) 0; - border-start-start-radius: var(--tab-radius, 0.5rem); - border-start-end-radius: var(--tab-radius, 0.5rem); - border-bottom-color: var(--tab-border-color); - -webkit-padding-start: var(--tab-padding, 1rem); - padding-inline-start: var(--tab-padding, 1rem); - -webkit-padding-end: var(--tab-padding, 1rem); - padding-inline-end: var(--tab-padding, 1rem); - padding-top: var(--tab-border, 1px); -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]), - .tabs-lifted .tab:is(input:checked){ - background-color: var(--tab-bg); - border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); - border-inline-start-color: var(--tab-border-color); - border-inline-end-color: var(--tab-border-color); - border-top-color: var(--tab-border-color); - -webkit-padding-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - -webkit-padding-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-bottom: var(--tab-border, 1px); - padding-top: 0; -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked):before{ - z-index: 1; - content: ""; - display: block; - position: absolute; - width: calc(100% + var(--tab-radius, 0.5rem) * 2); - height: var(--tab-radius, 0.5rem); - bottom: 0; - background-size: var(--tab-radius, 0.5rem); - background-position: top left, - top right; - background-repeat: no-repeat; - --tab-grad: calc(69% - var(--tab-border, 1px)); - --radius-start: radial-gradient( - circle at top left, - transparent var(--tab-grad), - var(--tab-border-color) calc(var(--tab-grad) + 0.25px), - var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), - var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) - ); - --radius-end: radial-gradient( - circle at top right, - transparent var(--tab-grad), - var(--tab-border-color) calc(var(--tab-grad) + 0.25px), - var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), - var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) - ); - background-image: var(--radius-start), var(--radius-end); -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted .tab:is(input:checked):first-child:before{ - background-image: var(--radius-end); - background-position: top right; -} - -[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):first-child:before{ - background-image: var(--radius-start); - background-position: top left; -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted .tab:is(input:checked):last-child:before{ - background-image: var(--radius-start); - background-position: top left; -} - -[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):last-child:before{ - background-image: var(--radius-end); - background-position: top right; -} - -.tabs-lifted - .tab-active:not(.tab-disabled):not([disabled]) - + .tabs-lifted - .tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before{ - background-image: var(--radius-end); - background-position: top right; -} - -.tabs-boxed .tab{ - border-radius: var(--rounded-btn, 0.5rem); -} - -.table tr.active, - .table tr.active:nth-child(even), - .table-zebra tbody tr:nth-child(even){ - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); -} - -.table-zebra tr.active, - .table-zebra tr.active:nth-child(even), - .table-zebra-zebra tbody tr:nth-child(even){ - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); -} - -.textarea-bordered{ - border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.textarea:focus{ - box-shadow: none; - border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.textarea-disabled, - .textarea:disabled, - .textarea[disabled]{ - cursor: not-allowed; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - --tw-text-opacity: 0.2; -} - -.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.textarea-disabled::placeholder, - .textarea:disabled::placeholder, - .textarea[disabled]::placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - @keyframes toast-pop{ 0%{ transform: scale(0.9); @@ -2376,36 +2067,6 @@ details.collapse summary::-webkit-details-marker{ place-items: end; } -.tabs-md :where(.tab){ - height: 2rem; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - --tab-padding: 1rem; -} - -.tabs-lg :where(.tab){ - height: 3rem; - font-size: 1.125rem; - line-height: 1.75rem; - line-height: 2; - --tab-padding: 1.25rem; -} - -.tabs-sm :where(.tab){ - height: 1.5rem; - font-size: 0.875rem; - line-height: .75rem; - --tab-padding: 0.75rem; -} - -.tabs-xs :where(.tab){ - height: 1.25rem; - font-size: 0.75rem; - line-height: .75rem; - --tab-padding: 0.5rem; -} - .avatar.online:before{ content: ""; position: absolute; @@ -2458,8 +2119,7 @@ details.collapse summary::-webkit-details-marker{ .join.join-horizontal > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - -webkit-margin-start: -1px; - margin-inline-start: -1px; + margin-inline-start: -1px; } .modal-top :where(.modal-box){ @@ -2589,6 +2249,46 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } +.\!left-0{ + left: 0px !important; +} + +.left-20{ + left: 5rem; +} + +.right-20{ + right: 5rem; +} + +.-left-0{ + left: -0px; +} + +.-left-20{ + left: -5rem; +} + +.-left-24{ + left: -6rem; +} + +.-left-28{ + left: -7rem; +} + +.-left-\[115px\]{ + left: -115px; +} + +.-left-\[105px\]{ + left: -105px; +} + +.-left-\[100px\]{ + left: -100px; +} + .z-\[1000\]{ z-index: 1000; } @@ -2617,16 +2317,16 @@ details.collapse summary::-webkit-details-marker{ margin: 0px; } -.my-0{ - margin-top: 0px; - margin-bottom: 0px; -} - .\!my-0{ margin-top: 0px !important; margin-bottom: 0px !important; } +.my-0{ + margin-top: 0px; + margin-bottom: 0px; +} + .mb-2{ margin-bottom: 0.5rem; } @@ -2647,10 +2347,6 @@ details.collapse summary::-webkit-details-marker{ margin-top: 0.5rem; } -.mt-24{ - margin-top: 6rem; -} - .mt-4{ margin-top: 1rem; } @@ -2671,10 +2367,6 @@ details.collapse summary::-webkit-details-marker{ display: none; } -.aspect-square{ - aspect-ratio: 1 / 1; -} - .h-3{ height: 0.75rem; } @@ -2704,22 +2396,22 @@ details.collapse summary::-webkit-details-marker{ min-height: 400px; } +.w-20{ + width: 5rem; +} + .w-3{ width: 0.75rem; } -.w-32{ - width: 8rem; -} - -.w-40{ - width: 10rem; -} - .w-56{ width: 14rem; } +.w-96{ + width: 24rem; +} + .w-\[24rem\]{ width: 24rem; } @@ -2728,10 +2420,6 @@ details.collapse summary::-webkit-details-marker{ width: 32rem; } -.w-\[550px\]{ - width: 550px; -} - .w-fit{ width: -moz-fit-content; width: fit-content; @@ -2741,30 +2429,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-10{ - width: 2.5rem; -} - -.w-14{ - width: 3.5rem; -} - -.w-24{ - width: 6rem; -} - -.w-20{ - width: 5rem; -} - -.w-64{ - width: 16rem; -} - -.w-96{ - width: 24rem; -} - .min-w-\[300px\]{ min-width: 300px; } @@ -2787,10 +2451,6 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.cursor-pointer{ - cursor: pointer; -} - .resize-none{ resize: none; } @@ -2950,11 +2610,6 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } -.bg-\[\#f6f6f6\]{ - --tw-bg-opacity: 1; - background-color: rgb(246 246 246 / var(--tw-bg-opacity)); -} - .bg-base-200{ --tw-bg-opacity: 1; background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); @@ -3182,17 +2837,6 @@ details.collapse summary::-webkit-details-marker{ outline-offset: 2px !important; } -.ring-1{ - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -.ring-black{ - --tw-ring-opacity: 1; - --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); -} - .filter{ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -3403,17 +3047,7 @@ img[src] { display: flex; } - .sm\:w-10{ - width: 2.5rem; - } - .sm\:w-32{ width: 8rem; } } - -@media (min-width: 768px){ - .md\:w-full{ - width: 100%; - } -} From 0f7b8cfe26772f2fa2dd892270290ab681fd7f1d Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 14:42:07 -0500 Subject: [PATCH 077/127] close dropdown --- js/AvatarPreview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index ed0c891..aad94e9 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -565,6 +565,7 @@ export function AvatarPreview() { class: () => "p-4 btn btn-ghost items-start", onclick: async (e) => { e.preventDefault(); + document.activeElement.blur() await loadJSONWorkflow(val); await new Promise((resolve) => setTimeout(resolve, 200)); const kSampler = app.graph.findNodesByType("KSampler")[0]; From b59acce30ce76658b63522390bf8faa6cdb55dbc Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 16 Nov 2023 14:08:13 -0800 Subject: [PATCH 078/127] auto enable and disable auto segment for workflow only starts with auto_segment --- js/AvatarPreview.js | 12 +++++++++++- js/index.js | 4 +++- js/state.js | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index aad94e9..fb15555 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -8,6 +8,7 @@ import { previewImgLoading, alertDialog, isGenerateFlow, + enableAutoSegment } from "./state.js"; const { button, iframe, div, img, input, label, span, textarea, ul, li } = van.tags; @@ -30,7 +31,16 @@ const workflowList = [ ]; // const workflowList = ["Auto_segment_workflow"]; +/** + * Load JSON workflow + * @param {string} name - The name of the workflow to load + */ async function loadJSONWorkflow(name) { + if (name === 'default' || name.toLowerCase().startsWith("Auto_segment")) { + enableAutoSegment.val = true + } else { + enableAutoSegment.val = false + } const json = await (await fetch(`./get_workflow?name=${name}`)).json(); app.loadGraphData(json); console.log(json); @@ -286,7 +296,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit Segment", + () => enableAutoSegment.val ? "Edit Segment (Auto)" : "Edit Segment", ), button( { diff --git a/js/index.js b/js/index.js index 6955d7f..527a1b8 100644 --- a/js/index.js +++ b/js/index.js @@ -16,6 +16,7 @@ import { shareLoading, previewModelId, embeddingID, + enableAutoSegment } from "./state.js"; import { van } from "./van.js"; import { app } from "./app.js"; @@ -344,7 +345,8 @@ function showMyImageEditor(node) { ); loadNpyTensor(embeedingUrl).then(async (tensor) => { embeddings.val = tensor; - await autoSegment(); + if (enableAutoSegment.val) + await autoSegment(); drawSegment(getClicks()); }); targetNode.val = node; diff --git a/js/state.js b/js/state.js index 7925d41..d60e46f 100644 --- a/js/state.js +++ b/js/state.js @@ -21,6 +21,7 @@ export const showPreview = van.state(true); export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=300&height=300&hideTrigger=true&voiceSelection=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); +export const enableAutoSegment = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); export const fileName = van.state(''); From 5093545ea851a006d5a62d546696ec0e785083d4 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 18:16:13 -0500 Subject: [PATCH 079/127] default load Auto_segment_workflow --- routes.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/routes.py b/routes.py index 24c7bef..8a6bd17 100644 --- a/routes.py +++ b/routes.py @@ -113,14 +113,17 @@ async def post_sam_model(request): @server.PromptServer.instance.routes.get("/get_workflow") async def get_workflow(request): name = request.rel_url.query.get("name", "default") + # if name == "default": + # json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' + # response = requests.get(json_link) + # response.raise_for_status() + # workflow = response.json() + # else: if name == "default": - json_link = 'https://cdn.discordapp.com/attachments/729003657483518063/1174045115757633596/girl1114.json?ex=656629b8&is=6553b4b8&hm=df3d7798b887e2b3b6b06ea438f1bc4ba041dd0f9daf54ea48101845ec7f4243&' - response = requests.get(json_link) - response.raise_for_status() - workflow = response.json() - else: - workflows_path = os.path.join(os.path.dirname(__file__), "workflow_templates") - workflow = json.load(open(f"{workflows_path}/{name}.json")) + name = "Auto_segment_workflow" + + workflows_path = os.path.join(os.path.dirname(__file__), "workflow_templates") + workflow = json.load(open(f"{workflows_path}/{name}.json")) return web.json_response(workflow) From f1b7a40981f493aae1cb985ed1845069a50b22ea Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 18:16:43 -0500 Subject: [PATCH 080/127] update new size --- js/state.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/state.js b/js/state.js index d60e46f..5819c73 100644 --- a/js/state.js +++ b/js/state.js @@ -9,7 +9,7 @@ * @typedef {Object} Point * @property {number} x - The x coordinate * @property {number} y - The y coordinate - * @property {number} label - The label + * @property {>} label - The label */ import { van } from "./van.js"; @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=300&height=300&hideTrigger=true&voiceSelection=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); export const enableAutoSegment = van.state(false); From 31b3288d8d352e64ebfab1c09f4c0cc3c8982d5f Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 18:17:08 -0500 Subject: [PATCH 081/127] add enable auto segment state --- js/AvatarPreview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index fb15555..e519164 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -36,7 +36,7 @@ const workflowList = [ * @param {string} name - The name of the workflow to load */ async function loadJSONWorkflow(name) { - if (name === 'default' || name.toLowerCase().startsWith("Auto_segment")) { + if (name === 'default' || name.toLowerCase().startsWith("auto_segment")) { enableAutoSegment.val = true } else { enableAutoSegment.val = false @@ -330,7 +330,7 @@ export function AvatarPreview() { div( { class: - "w-full mt-2 flex flex-col rounded-md left-0 top-0 z-[200]", + "w-full mt-2 flex flex-col rounded-md left-0 top-0", }, textarea({ class: From cfb7eb2ad5e2e0b7a89a82cb7abaafee9823a9f9 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Thu, 16 Nov 2023 23:56:52 -0500 Subject: [PATCH 082/127] fix: auto segment --- js/AvatarPreview.js | 466 +++++----- js/tw-styles.css | 2047 ++++++++++++++++++++++++++----------------- 2 files changed, 1480 insertions(+), 1033 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index e519164..5c28ba6 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -30,6 +30,22 @@ const workflowList = [ "SilverWolf_(Maid)", ]; +function editSegment(stage) { + /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + + const nodes = graph.findNodesByType("SAM MultiLayer"); + + /** @type {any[]}*/ + const widgets = nodes[0].widgets; + console.log(nodes[0]); + console.log(nodes[0].widgets); + widgets.find((x) => x.type == "button").callback(); + stage.val = 2; +} + // const workflowList = ["Auto_segment_workflow"]; /** * Load JSON workflow @@ -207,230 +223,266 @@ export function AvatarPreview() { }, !isGenerateFlow.val ? div( - { - class: () => - "flex flex-col justify-center items-center gap-4 w-full", - }, - div( - { class: () => "w-full flex mt-2" }, - button( - { - class: () => `btn w-full normal-case`, - onclick: async () => { - // previewImg.val = await uploadImage(); - // stage.val = 1; - var input = document.createElement("input"); - input.type = "file"; - - document.body.appendChild(input); - - // when the input content changes, do something - input.onchange = async function (e) { - stage.val = 1; - if (Object.entries(e.target.files).length) { - await uploadFile(e.target.files[0], true); - } - previewImg.val = URL.createObjectURL(e.target.files[0]); - // upload files - document.body.removeChild(input); - }; - - // Trigger file browser - input.click(); - }, - }, - div({ class: "badge badge-neutral" }, "1"), - div("Upload your image"), - span({ - class: "iconify text-lg", - "data-icon": "material-symbols:drive-folder-upload", - "data-inline": "false", - }), - () => - previewImgLoading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "", - ), - ), - () => { - const dnd = div( - { - id: "dnd", - class: () => - "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center text-black", - }, - "or drag and drop the image here", - ); - const image = img({ - class: () => "z-[10] object-contain w-full h-[394px] border", - src: previewImg, - }); - - if (isMobileDevice()) { - return previewImg.val !== "" ? image : ""; - } else { - return previewImg.val === "" ? dnd : image; - } - }, + { + class: () => + "flex flex-col justify-center items-center gap-4 w-full", + }, + div( + { class: () => "w-full flex mt-2" }, button( { - class: () => - "btn w-full normal-case " + - (stage.val < 1 ? "btn-disabled" : ""), - onclick: () => { - /** @type {import('../../../web/types/litegraph.js').LGraph}*/ - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - - const nodes = graph.findNodesByType("SAM MultiLayer"); - - /** @type {any[]}*/ - const widgets = nodes[0].widgets; - console.log(nodes[0]); - console.log(nodes[0].widgets); - widgets.find((x) => x.type == "button").callback(); - stage.val = 2; - }, - }, - div({ class: "badge badge-neutral" }, "2"), - () => enableAutoSegment.val ? "Edit Segment (Auto)" : "Edit Segment", - ), - button( - { - class: () => - "btn w-full normal-case " + - (stage.val < 2 ? "btn-disabled" : ""), + class: () => `btn w-full normal-case`, onclick: async () => { - // const uploaded = await uploadSegments(); - // if (!uploaded) return; + // previewImg.val = await uploadImage(); + // stage.val = 1; + var input = document.createElement("input"); + input.type = "file"; - const graph = app.graph; - const imageNodes = graph.findNodesByType("LoadImage"); - if (!imageNodes[0].imgs) return; - document.getElementById("queue-button").click(); - }, - }, - div({ class: "badge badge-neutral" }, "3"), - () => - loading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "Make It Alive!", - ), - ) - : div( - { - class: - "flex flex-col justify-center items-center gap-4 w-full text-black", - }, - div( - { - class: - "w-full mt-2 flex flex-col rounded-md left-0 top-0", - }, - textarea({ - class: - "textarea textarea-bordered border-gray-300 border-b-0 focus:outline-none resize-none rounded-t-md rounded-b-none text-md h-36", - placeholder: "Enter your prompt", - defaultValue: - "1girl, looking at viewer, open mouth, simple background, white background, smile", - id: "positivePromptProxy", - }), - div( - { - class: - "flex flex-row gap-2 border border-gray-300 rounded-b-md text-md items-center", - }, - span({ class: "ml-4" }, "Seed"), - div({ class: "divider divider-horizontal m-0" }), - input({ - type: "text", - class: "input border-none focus:outline-none w-full p-0", - placeholder: "Seed", - defaultValue: "1234", - id: "seedProxy", - }), - div( - { - onclick: () => { - const random4Digits = - Math.floor(Math.random() * 9000) + 1000; - console.log( - random4Digits, - document.getElementById("seedProxy").value, - ); - document.getElementById("seedProxy").value = - random4Digits.toString(); - }, - }, - span({ - class: "iconify text-2xl mr-4 hover:cursor-pointer", - "data-icon": "fad:random-1dice", - "data-inline": "false", - }), - ), - ), - ), - button( - { - class: "btn w-full normal-case ", - onclick: async () => { - loading.val = true; + document.body.appendChild(input); - updatePositivePrompt( - app, - document.getElementById("positivePromptProxy").value, - ); - updateSeedValue( - app, - document.getElementById("seedProxy").value, - ); + // when the input content changes, do something + input.onchange = async function (e) { + stage.val = 1; + if (Object.entries(e.target.files).length) { + await uploadFile(e.target.files[0], true); + } + previewImg.val = URL.createObjectURL(e.target.files[0]); + // upload files + document.body.removeChild(input); + }; - const sam = app.graph.findNodesByType("SAM MultiLayer")[0]; - if (!sam) { - alertDialog.val = { - text: "Cannot find the SAM node. Please make sure the workflow is correct.", - time: 5000, - }; - return; - } - const ckpt = sam.widgets[0].value; - const modelType = ckpt.match(/vit_[lbh]/)?.[0]; - await initModel(modelType); - await uploadSegments(); - - document.getElementById("queue-button").click(); + // Trigger file browser + input.click(); }, }, div({ class: "badge badge-neutral" }, "1"), + div("Upload your image"), + span({ + class: "iconify text-lg", + "data-icon": "material-symbols:drive-folder-upload", + "data-inline": "false", + }), () => - loading.val - ? span({ class: "loading loading-spinner loading-md" }) - : "Make It Alive!", + previewImgLoading.val + ? span({ + class: "loading loading-spinner loading-md", + }) + : "", ), + ), + () => { + const dnd = div( + { + id: "dnd", + class: () => + "h-96 w-full border-2 border-black border-dashed items-center rounded-lg flex justify-center text-black", + }, + "or drag and drop the image here", + ); + const image = img({ + class: () => "z-[10] object-contain w-full h-[394px] border", + src: previewImg, + }); + + if (isMobileDevice()) { + return previewImg.val !== "" ? image : ""; + } else { + return previewImg.val === "" ? dnd : image; + } + }, + div( + { + class: "grid grid-cols-2 gap-2 w-full", + }, button( { - class: "btn w-full normal-case", + class: () => + "btn normal-case " + + (stage.val < 1 ? "btn-disabled" : ""), onclick: () => { - /** @type {import('../../../web/types/litegraph.js').LGraph}*/ - const graph = app.graph; - const nodes = graph.findNodesByType("SAM MultiLayer"); - - /** @type {any[]}*/ - const widgets = nodes[0].widgets; - console.log(nodes[0]); - console.log(nodes[0].widgets); - widgets.find((x) => x.type == "button").callback(); + enableAutoSegment.val = false; + editSegment(stage) }, }, div({ class: "badge badge-neutral" }, "2"), - "(Optional) Edit Segment", + "Edit Segment", + ), + button( + { + class: () => + "btn normal-case " + + (stage.val < 1 ? "btn-disabled" : ""), + onclick: () => { + enableAutoSegment.val = true; + editSegment(stage) + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Edit Segment (Auto)", ), ), + button( + { + class: () => + "btn w-full normal-case " + + (stage.val < 2 ? "btn-disabled" : ""), + onclick: async () => { + // const uploaded = await uploadSegments(); + // if (!uploaded) return; + + const graph = app.graph; + const imageNodes = graph.findNodesByType("LoadImage"); + if (!imageNodes[0].imgs) return; + document.getElementById("queue-button").click(); + }, + }, + div({ class: "badge badge-neutral" }, "3"), + () => + loading.val + ? span({ + class: "loading loading-spinner loading-md", + }) + : "Make It Alive!", + ), + ) + : div( + { + class: + "flex flex-col justify-center items-center gap-4 w-full text-black", + }, + div( + { + class: + "w-full mt-2 flex flex-col rounded-md left-0 top-0", + }, + textarea({ + class: + "textarea textarea-bordered border-gray-300 border-b-0 focus:outline-none resize-none rounded-t-md rounded-b-none text-md h-36", + placeholder: "Enter your prompt", + defaultValue: + "1girl, looking at viewer, open mouth, simple background, white background, smile", + id: "positivePromptProxy", + }), + div( + { + class: + "flex flex-row gap-2 border border-gray-300 rounded-b-md text-md items-center", + }, + span({ class: "ml-4" }, "Seed"), + div({ class: "divider divider-horizontal m-0" }), + input({ + type: "text", + class: "input border-none focus:outline-none w-full p-0", + placeholder: "Seed", + defaultValue: "1234", + id: "seedProxy", + }), + div( + { + onclick: () => { + const random4Digits = + Math.floor(Math.random() * 9000) + 1000; + console.log( + random4Digits, + document.getElementById("seedProxy").value, + ); + document.getElementById("seedProxy").value = + random4Digits.toString(); + }, + }, + span({ + class: "iconify text-2xl mr-4 hover:cursor-pointer", + "data-icon": "fad:random-1dice", + "data-inline": "false", + }), + ), + ), + ), + button( + { + class: "btn w-full normal-case ", + onclick: async () => { + loading.val = true; + + updatePositivePrompt( + app, + document.getElementById("positivePromptProxy").value, + ); + updateSeedValue( + app, + document.getElementById("seedProxy").value, + ); + + const sam = app.graph.findNodesByType("SAM MultiLayer")[0]; + if (!sam) { + alertDialog.val = { + text: "Cannot find the SAM node. Please make sure the workflow is correct.", + time: 5000, + }; + return; + } + const ckpt = sam.widgets[0].value; + const modelType = ckpt.match(/vit_[lbh]/)?.[0]; + await initModel(modelType); + await uploadSegments(); + + document.getElementById("queue-button").click(); + }, + }, + div({ class: "badge badge-neutral" }, "1"), + () => + loading.val + ? span({ class: "loading loading-spinner loading-md" }) + : "Make It Alive!", + ), + div( + { + class: "grid grid-cols-2 gap-2 w-full" + }, + button( + { + class: () => + "btn normal-case ", + onclick: () => { + enableAutoSegment.val = false; + editSegment(stage) + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Edit Segment", + ), + button( + { + class: () => + "btn normal-case ", + onclick: () => { + enableAutoSegment.val = true; + editSegment(stage) + }, + }, + div({ class: "badge badge-neutral" }, "2"), + "Edit Segment (Auto)", + ), + ) + // button( + // { + // class: "btn w-full normal-case", + // onclick: () => { + // /** @type {import('../../../web/types/litegraph.js').LGraph}*/ + // const graph = app.graph; + // const nodes = graph.findNodesByType("SAM MultiLayer"); + + // /** @type {any[]}*/ + // const widgets = nodes[0].widgets; + // console.log(nodes[0]); + // console.log(nodes[0].widgets); + // widgets.find((x) => x.type == "button").callback(); + // }, + // }, + // div({ class: "badge badge-neutral" }, "2"), + // "(Optional) Edit Segment", + // ), + ), ), ); }; @@ -500,8 +552,8 @@ export function AvatarPreview() { () => shareLoading.val ? span({ - class: "loading loading-spinner loading-md", - }) + class: "loading loading-spinner loading-md", + }) : "Get Avatar Link", ), ), diff --git a/js/tw-styles.css b/js/tw-styles.css index 85884e3..9d4d538 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ /* @@ -640,18 +640,6 @@ html{ justify-items: center; gap: 1rem; text-align: center; -} - -@media (min-width: 640px){ - .alert{ - grid-auto-flow: column; - grid-template-columns: auto minmax(auto,1fr); - justify-items: start; - text-align: start; - } -} - -.alert{ border-radius: var(--rounded-box, 1rem); border-width: 1px; --tw-border-opacity: 1; @@ -664,6 +652,15 @@ html{ background-color: var(--alert-bg); } +@media (min-width: 640px){ + .alert{ + grid-auto-flow: column; + grid-template-columns: auto minmax(auto,1fr); + justify-items: start; + text-align: start; + } +} + .avatar{ position: relative; display: inline-flex; @@ -696,8 +693,8 @@ html{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; height: 1.25rem; font-size: 0.875rem; line-height: 1.25rem; @@ -715,29 +712,24 @@ html{ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } -.badge-neutral{ - --tw-border-opacity: 1; - border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); -} +@media (hover:hover){ + .label a:hover{ + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + } -.badge-outline.badge-neutral{ - --tw-text-opacity: 1; - color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); -} + .menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active{ + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + } -.btm-nav > *:where(.active){ - border-top-width: 2px; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.btm-nav > * .label{ - font-size: 1rem; - line-height: 1.5rem; + .tab:hover{ + --tw-text-opacity: 1; + } } .btn{ @@ -754,11 +746,27 @@ html{ justify-content: center; border-radius: var(--rounded-btn, 0.5rem); border-color: transparent; + border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)); padding-left: 1rem; padding-right: 1rem; text-align: center; font-size: 0.875rem; line-height: 1em; + gap: 0.5rem; + font-weight: 600; + text-decoration-line: none; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; + transition-property: color, background-color, border-color, opacity, box-shadow, transform; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)); } .btn-disabled, @@ -774,161 +782,6 @@ html{ padding: 0px; } -.btn{ - gap: 0.5rem; - font-weight: 600; - text-decoration-line: none; - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - border-width: var(--border-btn, 1px); - animation: button-pop var(--animation-btn, 0.25s) ease-out; - transition-property: color, background-color, border-color, opacity, box-shadow, transform; -} - -.btn:active:hover, - .btn:active:focus{ - animation: button-pop 0s ease-out; - transform: scale(var(--btn-focus-scale, 0.97)); -} - -.btn{ - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - text-decoration-line: none; - --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); - background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)); - border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)); -} - -@supports not (color: oklch(0 0 0)){ - .btn{ - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } -} - -@media (hover: hover){ - .btn:hover{ - --tw-border-opacity: 1; - border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); - background-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, - black - ); - border-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, - black - ); - } - - @supports not (color: oklch(0 0 0)){ - .btn:hover{ - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } - } -} - -.btn:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; -} - -.btn-neutral{ - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - outline-color: var(--fallback-n,oklch(var(--n)/1)); -} - -@supports (color: oklch(0 0 0)){ - .btn-neutral{ - --btn-color: var(--n); - } -} - -@supports not (color: oklch(0 0 0)){ - .btn-neutral{ - --btn-color: var(--fallback-n); - } -} - -.btn.glass{ - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: currentColor; -} - -@media (hover: hover){ - .btn.glass:hover{ - --glass-opacity: 25%; - --glass-border-opacity: 15%; - } -} - -.btn.glass.btn-active{ - --glass-opacity: 25%; - --glass-border-opacity: 15%; -} - -.btn-ghost{ - border-width: 1px; - border-color: transparent; - background-color: transparent; - color: currentColor; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: currentColor; -} - -@media (hover: hover){ - .btn-ghost:hover{ - border-color: transparent; - } - - @supports (color: oklch(0 0 0)){ - .btn-ghost:hover{ - background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); - } - } -} - -.btn-ghost.btn-active{ - border-color: transparent; - background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.btn.btn-disabled, - .btn[disabled], - .btn:disabled{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - -@media (hover: hover){ - .btn-disabled:hover, - .btn[disabled]:hover, - .btn:disabled:hover{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; - } -} - :where(.btn:is(input[type="checkbox"])), :where(.btn:is(input[type="radio"])){ width: auto; @@ -1008,6 +861,10 @@ html{ flex-direction: row; align-items: center; align-self: stretch; + margin-top: 1rem; + margin-bottom: 1rem; + height: 1rem; + white-space: nowrap; } .divider:before, @@ -1017,18 +874,8 @@ html{ flex-grow: 1; --tw-content: ''; content: var(--tw-content); - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); -} - -.divider{ - margin-top: 1rem; - margin-bottom: 1rem; - height: 1rem; - white-space: nowrap; -} - -.divider:not(:empty){ - gap: 1rem; + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } .dropdown{ @@ -1056,8 +903,8 @@ html{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; } .dropdown-end .dropdown-content{ @@ -1111,16 +958,150 @@ html{ .dropdown.dropdown-hover:hover .dropdown-content{ visibility: visible; opacity: 1; + } + + .btn:hover{ + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, + black + ); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, + black + ); + } + + @supports not (color: oklch(0 0 0)){ + .btn:hover{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + } + + .btn.glass:hover{ + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } + + .btn-ghost:hover{ + border-color: transparent; + } + + @supports (color: oklch(0 0 0)){ + .btn-ghost:hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + } + } + + .btn-disabled:hover, + .btn[disabled]:hover, + .btn:disabled:hover{ + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + + .dropdown.dropdown-hover:hover .dropdown-content{ --tw-scale-x: 1; --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } + + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ + cursor: pointer; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; + } + + @supports (color: oklch(0 0 0)){ + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + } + } + + .tab[disabled], + .tab[disabled]:hover{ + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } } .dropdown:is(details) summary::-webkit-details-marker{ display: none; } +.file-input{ + height: 3rem; + flex-shrink: 1; + -webkit-padding-end: 1rem; + padding-inline-end: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + overflow: hidden; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.file-input::file-selector-button{ + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; + display: inline-flex; + height: 100%; + flex-shrink: 0; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 1em; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + border-style: solid; + --tw-border-opacity: 1; + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + font-weight: 600; + text-transform: uppercase; + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + text-decoration-line: none; + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; +} + .label{ display: flex; -webkit-user-select: none; @@ -1134,28 +1115,615 @@ html{ padding-bottom: 0.5rem; } -@media(hover:hover){ - .label a:hover{ - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - } -} - .input{ flex-shrink: 1; height: 3rem; padding-left: 1rem; padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.join{ + display: inline-flex; + align-items: stretch; + border-radius: var(--rounded-btn, 0.5rem); +} + +.join :where(.join-item){ + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item{ + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item{ + border-start-end-radius: 0; + border-end-end-radius: 0; +} + +.join .dropdown .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .dropdown .join-item{ + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item){ + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item{ + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item){ + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +@supports not selector(:has(*)){ + :where(.join *){ + border-radius: inherit; + } +} + +@supports selector(:has(*)){ + :where(.join *:has(.join-item)){ + border-radius: inherit; + } +} + +.link{ + cursor: pointer; + text-decoration-line: underline; +} + +.mask{ + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +.menu{ + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.5rem; +} + +.menu :where(li ul){ + position: relative; + white-space: nowrap; + -webkit-margin-start: 1rem; + margin-inline-start: 1rem; + -webkit-padding-start: 0.5rem; + padding-inline-start: 0.5rem; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), + .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: 0.5rem; + grid-auto-columns: minmax(auto, max-content) auto max-content; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.menu li.disabled{ + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + color: var(--fallback-bc,oklch(var(--bc)/0.3)); +} + +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ + display: none; +} + +:where(.menu li){ + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; +} + +:where(.menu li) .badge{ + justify-self: end; +} + +.modal{ + pointer-events: none; + position: fixed; + inset: 0px; + margin: 0px; + display: grid; + height: 100%; + max-height: none; + width: 100%; + max-width: none; + justify-items: center; + padding: 0px; + opacity: 0; + overscroll-behavior: contain; + z-index: 999; + background-color: transparent; + color: inherit; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity, visibility; + overflow-y: hidden; +} + +:where(.modal){ + align-items: center; +} + +.modal-box{ + max-height: calc(100vh - 5em); + grid-column-start: 1; + grid-row-start: 1; + width: 91.666667%; + max-width: 32rem; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + padding: 1.5rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; + overflow-y: auto; + overscroll-behavior: contain; +} + +.modal-open, +.modal:target, +.modal-toggle:checked + .modal, +.modal[open]{ + pointer-events: auto; + visibility: visible; + opacity: 1; +} + +.modal-action{ + display: flex; + margin-top: 1.5rem; + justify-content: flex-end; +} + +:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])){ + overflow: hidden; +} + +.radio{ + flex-shrink: 0; + --chkbg: var(--bc); + height: 1.5rem; + width: 1.5rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 9999px; + border-width: 1px; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; +} + +.tabs{ + display: grid; + align-items: flex-end; +} + +.tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(.tab-active), + .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(.tab-active){ + border-bottom-color: transparent; +} + +.tab{ + position: relative; + grid-row-start: 1; + display: inline-flex; + height: 2rem; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; + --tw-text-opacity: 0.5; + --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); + --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); + --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); + color: var(--tab-color); + -webkit-padding-start: var(--tab-padding, 1rem); + padding-inline-start: var(--tab-padding, 1rem); + -webkit-padding-end: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); +} + +.tab:is(input[type="radio"]):after{ + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.tab:not(input):empty{ + cursor: default; + grid-column-start: span 9999; +} + +.tab-content{ + grid-column: 1 / -1; + grid-column-end: span 9999; + grid-row-start: 2; + margin-top: calc(var(--tab-border) * -1); + display: none; + border-color: transparent; + border-width: var(--tab-border, 0); +} + +:checked + .tab-content:nth-child(2), + .tab-active + .tab-content:nth-child(2){ + border-start-start-radius: 0px; +} + +input.tab:checked + .tab-content, +.tab-active + .tab-content{ + display: block; +} + +.textarea{ + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; line-height: 2; border-radius: var(--rounded-btn, 0.5rem); border-width: 1px; border-color: transparent; --tw-bg-opacity: 1; background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.avatar-group :where(.avatar){ + overflow: hidden; + border-radius: 9999px; + border-width: 4px; + --tw-border-opacity: 1; + border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); +} + +.badge-neutral{ + --tw-border-opacity: 1; + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.badge-outline.badge-neutral{ + --tw-text-opacity: 1; + color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); +} + +.btm-nav > *:where(.active){ + border-top-width: 2px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.btm-nav > * .label{ font-size: 1rem; line-height: 1.5rem; } +.btn:active:hover, + .btn:active:focus{ + animation: button-pop 0s ease-out; + transform: scale(var(--btn-focus-scale, 0.97)); +} + +@supports not (color: oklch(0 0 0)){ + .btn{ + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + + .btn-neutral{ + --btn-color: var(--fallback-n); + } +} + +.btn:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +@supports (color: oklch(0 0 0)){ + .btn-neutral{ + --btn-color: var(--n); + } +} + +.btn-neutral{ + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + outline-color: var(--fallback-n,oklch(var(--n)/1)); +} + +.btn.glass{ + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn.glass.btn-active{ + --glass-opacity: 25%; + --glass-border-opacity: 15%; +} + +.btn-ghost{ + border-width: 1px; + border-color: transparent; + background-color: transparent; + color: currentColor; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn-ghost.btn-active{ + border-color: transparent; + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.btn.btn-disabled, + .btn[disabled], + .btn:disabled{ + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.btn:is(input[type="checkbox"]:checked), +.btn:is(input[type="radio"]:checked){ + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@keyframes button-pop{ + 0%{ + transform: scale(var(--btn-focus-scale, 0.98)); + } + + 40%{ + transform: scale(1.02); + } + + 100%{ + transform: scale(1); + } +} + +@keyframes checkmark{ + 0%{ + background-position-y: 5px; + } + + 50%{ + background-position-y: -2px; + } + + 100%{ + background-position-y: 0; + } +} + +details.collapse{ + width: 100%; +} + +details.collapse summary{ + position: relative; + display: block; + outline: 2px solid transparent; + outline-offset: 2px; +} + +details.collapse summary::-webkit-details-marker{ + display: none; +} + +.collapse:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapse:has(.collapse-title:focus-visible), +.collapse:has(> input[type="checkbox"]:focus-visible), +.collapse:has(> input[type="radio"]:focus-visible){ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], +.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), +.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title{ + cursor: pointer; +} + +.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title{ + cursor: unset; +} + +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]){ + z-index: 1; +} + +.collapse-title, +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]){ + width: 100%; + padding: 1rem; + -webkit-padding-end: 3rem; + padding-inline-end: 3rem; + min-height: 3.75rem; + transition: background-color 0.2s ease-out; +} + +.collapse[open] > :where(.collapse-content), +.collapse-open > :where(.collapse-content), +.collapse:focus:not(.collapse-close) > :where(.collapse-content), +.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), +.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content){ + padding-bottom: 1rem; + transition: padding 0.2s ease-out, + background-color 0.2s ease-out; +} + +.collapse[open].collapse-arrow > .collapse-title:after, +.collapse-open.collapse-arrow > .collapse-title:after, +.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ + --tw-translate-y: -50%; + --tw-rotate: 225deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.collapse[open].collapse-plus > .collapse-title:after, +.collapse-open.collapse-plus > .collapse-title:after, +.collapse-plus:focus:not(.collapse-close) > .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ + content: "−"; +} + +.divider:not(:empty){ + gap: 1rem; +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:focus .dropdown-content, +.dropdown:focus-within .dropdown-content{ + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.file-input-bordered{ + --tw-border-opacity: 0.2; +} + +.file-input:focus{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.file-input-disabled, + .file-input[disabled]{ + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.file-input-disabled::-moz-placeholder, .file-input[disabled]::-moz-placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.file-input-disabled::placeholder, + .file-input[disabled]::placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.file-input-disabled::file-selector-button, .file-input[disabled]::file-selector-button{ + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + .input input:focus{ outline: 2px solid transparent; outline-offset: 2px; @@ -1202,81 +1770,11 @@ html{ --tw-placeholder-opacity: 0.2; } -.join{ - display: inline-flex; - align-items: stretch; -} - -.join :where(.join-item){ - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:not(:first-child):not(:last-child), - .join *:not(:first-child):not(:last-child) .join-item{ - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .join-item{ - border-start-end-radius: 0; - border-end-end-radius: 0; -} - -.join .dropdown .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .dropdown .join-item{ - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -.join :where(.join-item:first-child:not(:last-child)), - .join :where(*:first-child:not(:last-child) .join-item){ - border-end-start-radius: inherit; - border-start-start-radius: inherit; -} - -.join .join-item:last-child:not(:first-child), - .join *:last-child:not(:first-child) .join-item{ - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join :where(.join-item:last-child:not(:first-child)), - .join :where(*:last-child:not(:first-child) .join-item){ - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -.join{ - border-radius: var(--rounded-btn, 0.5rem); -} - .join > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-inline-start: -1px; -} - -@supports not selector(:has(*)){ - :where(.join *){ - border-radius: inherit; - } -} - -@supports selector(:has(*)){ - :where(.join *:has(.join-item)){ - border-radius: inherit; - } -} - -.link{ - cursor: pointer; - text-decoration-line: underline; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .link:focus{ @@ -1289,67 +1787,122 @@ html{ outline-offset: 2px; } -.mask{ - -webkit-mask-size: contain; - mask-size: contain; +.loading{ + pointer-events: none; + display: inline-block; + aspect-ratio: 1 / 1; + width: 1.5rem; + background-color: currentColor; + -webkit-mask-size: 100%; + mask-size: 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.menu{ - display: flex; - flex-direction: column; - flex-wrap: wrap; - font-size: 0.875rem; - line-height: 1.25rem; +.loading-spinner{ + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); } -.menu :where(li ul){ - position: relative; - white-space: nowrap; +.loading-md{ + width: 1.5rem; +} + +.loading-lg{ + width: 2.5rem; +} + +:where(.menu li:empty){ + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + margin: 0.5rem 1rem; + height: 1px; +} + +.menu :where(li ul):before{ + position: absolute; + bottom: 0.75rem; + inset-inline-start: 0px; + top: 0.75rem; + width: 1px; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + content: ""; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), - .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ - display: grid; - grid-auto-flow: column; - align-content: flex-start; - align-items: center; - gap: 0.5rem; - grid-auto-columns: minmax(auto, max-content) auto max-content; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: start; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + text-wrap: balance; } -.menu li.disabled{ - cursor: not-allowed; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; +:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ + cursor: pointer; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; } -.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)){ +.menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active{ + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.menu :where(li > details > summary)::-webkit-details-marker{ display: none; } -.menu{ - padding: 0.5rem; -} - -:where(.menu li){ - position: relative; - display: flex; - flex-shrink: 0; - flex-direction: column; - flex-wrap: wrap; - align-items: stretch; -} - -:where(.menu li) .badge{ +.menu :where(li > details > summary):after, +.menu :where(li > .menu-dropdown-toggle):after{ justify-self: end; + display: block; + margin-top: -0.5rem; + height: 0.5rem; + width: 0.5rem; + transform: rotate(45deg); + transition-property: transform, margin-top; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.menu :where(li > details[open] > summary):after, +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after{ + transform: rotate(225deg); + margin-top: 0; +} + +.mockup-phone .display{ + overflow: hidden; + border-radius: 40px; + margin-top: -25px; } .mockup-browser .mockup-browser-toolbar .input{ @@ -1398,464 +1951,12 @@ html{ opacity: 0.6; } -.mockup-phone .display{ - overflow: hidden; - border-radius: 40px; - margin-top: -25px; -} - -.modal{ - pointer-events: none; - position: fixed; - inset: 0px; - margin: 0px; - display: grid; - height: 100%; - max-height: none; - width: 100%; - max-width: none; - justify-items: center; - padding: 0px; - opacity: 0; - overscroll-behavior: contain; - z-index: 999; - background-color: transparent; - color: inherit; - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-property: transform, opacity, visibility; - overflow-y: hidden; -} - .modal:not(dialog:not(.modal-open)), .modal::backdrop{ background-color: rgba(0, 0, 0, 0.3); animation: modal-pop 0.2s ease-out; } -:where(.modal){ - align-items: center; -} - -.modal-box{ - max-height: calc(100vh - 5em); - grid-column-start: 1; - grid-row-start: 1; - width: 91.666667%; - max-width: 32rem; - --tw-scale-x: .9; - --tw-scale-y: .9; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); - border-bottom-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); - border-top-left-radius: var(--rounded-box, 1rem); - border-top-right-radius: var(--rounded-box, 1rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); - padding: 1.5rem; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; - overflow-y: auto; - overscroll-behavior: contain; -} - -.modal-open, -.modal:target, -.modal-toggle:checked + .modal, -.modal[open]{ - pointer-events: auto; - visibility: visible; - opacity: 1; -} - -.modal-action{ - display: flex; - margin-top: 1.5rem; - justify-content: flex-end; -} - -.modal-action > :not([hidden]) ~ :not([hidden]){ - --tw-space-x-reverse: 0; - margin-right: calc(0.5rem * var(--tw-space-x-reverse)); - margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); -} - -:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])){ - overflow: hidden; -} - -.table tr.active, - .table tr.active:nth-child(even), - .table-zebra tbody tr:nth-child(even){ - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); -} - -.table-zebra tr.active, - .table-zebra tr.active:nth-child(even), - .table-zebra-zebra tbody tr:nth-child(even){ - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); -} - -.textarea{ - min-height: 3rem; - flex-shrink: 1; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - border-radius: var(--rounded-btn, 0.5rem); - border-width: 1px; - border-color: transparent; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.textarea-bordered{ - border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.textarea:focus{ - box-shadow: none; - border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.textarea-disabled, - .textarea:disabled, - .textarea[disabled]{ - cursor: not-allowed; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - --tw-text-opacity: 0.2; -} - -.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.textarea-disabled::placeholder, - .textarea:disabled::placeholder, - .textarea[disabled]::placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.avatar-group :where(.avatar){ - overflow: hidden; - border-radius: 9999px; - border-width: 4px; - --tw-border-opacity: 1; - border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); -} - -.btn:is(input[type="checkbox"]:checked), -.btn:is(input[type="radio"]:checked){ - --tw-border-opacity: 1; - border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - -@media (hover: hover){ - .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover{ - background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - } -} - -.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible{ - outline-color: var(--fallback-p,oklch(var(--p)/1)); -} - -@keyframes button-pop{ - 0%{ - transform: scale(var(--btn-focus-scale, 0.98)); - } - - 40%{ - transform: scale(1.02); - } - - 100%{ - transform: scale(1); - } -} - -@keyframes checkmark{ - 0%{ - background-position-y: 5px; - } - - 50%{ - background-position-y: -2px; - } - - 100%{ - background-position-y: 0; - } -} - -details.collapse{ - width: 100%; -} - -details.collapse summary{ - position: relative; - display: block; -} - -details.collapse summary::-webkit-details-marker{ - display: none; -} - -.collapse:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -details.collapse summary{ - outline: 2px solid transparent; - outline-offset: 2px; -} - -.collapse:has(.collapse-title:focus-visible), -.collapse:has(> input[type="checkbox"]:focus-visible), -.collapse:has(> input[type="radio"]:focus-visible){ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], -.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), -.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title{ - cursor: pointer; -} - -.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title{ - cursor: unset; -} - -:where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]){ - z-index: 1; -} - -.collapse-title, -:where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]){ - width: 100%; - padding: 1rem; - padding-inline-end: 3rem; - min-height: 3.75rem; - transition: background-color 0.2s ease-out; -} - -.collapse[open] > :where(.collapse-content), -.collapse-open > :where(.collapse-content), -.collapse:focus:not(.collapse-close) > :where(.collapse-content), -.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), -.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content){ - padding-bottom: 1rem; - transition: padding 0.2s ease-out, - background-color 0.2s ease-out; -} - -.collapse[open].collapse-arrow > .collapse-title:after, -.collapse-open.collapse-arrow > .collapse-title:after, -.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, -.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ - --tw-translate-y: -50%; - --tw-rotate: 225deg; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.collapse[open].collapse-plus > .collapse-title:after, -.collapse-open.collapse-plus > .collapse-title:after, -.collapse-plus:focus:not(.collapse-close) > .collapse-title:after, -.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, -.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after{ - content: "−"; -} - -.dropdown.dropdown-open .dropdown-content, -.dropdown:focus .dropdown-content, -.dropdown:focus-within .dropdown-content{ - --tw-scale-x: 1; - --tw-scale-y: 1; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.loading{ - pointer-events: none; - display: inline-block; - aspect-ratio: 1 / 1; - width: 1.5rem; - background-color: currentColor; - -webkit-mask-size: 100%; - mask-size: 100%; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-position: center; - mask-position: center; - -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); -} - -.loading-spinner{ - -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); -} - -.loading-md{ - width: 1.5rem; -} - -.loading-lg{ - width: 2.5rem; -} - -:where(.menu li:empty){ - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - margin: 0.5rem 1rem; - height: 1px; -} - -.menu :where(li ul){ - margin-inline-start: 1rem; - padding-inline-start: 0.5rem; -} - -.menu :where(li ul):before{ - position: absolute; - bottom: 0.75rem; - inset-inline-start: 0px; - top: 0.75rem; - width: 1px; - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - content: ""; -} - -.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), -.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)){ - border-radius: var(--rounded-btn, 0.5rem); - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - text-align: start; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - text-wrap: balance; -} - -:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, - :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible{ - cursor: pointer; - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - outline: 2px solid transparent; - outline-offset: 2px; -} - -@media (hover: hover){ - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ - cursor: pointer; - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - outline: 2px solid transparent; - outline-offset: 2px; - } - - @supports (color: oklch(0 0 0)){ - :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover{ - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); - } - } -} - -.menu li > *:not(ul):not(.menu-title):not(details):active, -.menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active{ - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); -} - -@media(hover:hover){ - .menu li > *:not(ul):not(.menu-title):not(details):active, -.menu li > *:not(ul):not(.menu-title):not(details).active, -.menu li > details > summary:active{ - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - } -} - -.menu li.disabled{ - color: var(--fallback-bc,oklch(var(--bc)/0.3)); -} - -.menu :where(li > details > summary)::-webkit-details-marker{ - display: none; -} - -.menu :where(li > details > summary):after, -.menu :where(li > .menu-dropdown-toggle):after{ - justify-self: end; - display: block; - margin-top: -0.5rem; - height: 0.5rem; - width: 0.5rem; - transform: rotate(45deg); - transition-property: transform, margin-top; - transition-duration: 0.3s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - content: ""; - transform-origin: 75% 75%; - box-shadow: 2px 2px; - pointer-events: none; -} - -.menu :where(li > details[open] > summary):after, -.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after{ - transform: rotate(225deg); - margin-top: 0; -} - .modal-open .modal-box, .modal-toggle:checked + .modal .modal-box, .modal:target .modal-box, @@ -1866,6 +1967,12 @@ details.collapse summary{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.modal-action > :not([hidden]) ~ :not([hidden]){ + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + @keyframes modal-pop{ 0%{ opacity: 0; @@ -1878,6 +1985,32 @@ details.collapse summary{ } } +.radio:focus{ + box-shadow: none; +} + +.radio:focus-visible{ + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.radio:checked, + .radio[aria-checked="true"]{ + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-image: none; + animation: radiomark var(--animation-input, 0.2s) ease-out; + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; +} + +.radio:disabled{ + cursor: not-allowed; + opacity: 0.2; +} + @keyframes radiomark{ 0%{ box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, @@ -1919,6 +2052,182 @@ details.collapse summary{ } } +.tabs-lifted .tab:focus-visible{ + border-end-end-radius: 0; + border-end-start-radius: 0; +} + +.tab.tab-active:not(.tab-disabled):not([disabled]), + .tab:is(input:checked){ + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 1; + --tw-text-opacity: 1; +} + +.tab:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + +.tab:focus-visible{ + outline: 2px solid currentColor; + outline-offset: -5px; +} + +.tab-disabled, + .tab[disabled]{ + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.tabs-bordered .tab{ + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; + border-style: solid; + border-bottom-width: calc(var(--tab-border, 1px) + 1px); +} + +.tabs-lifted .tab{ + border: var(--tab-border, 1px) solid transparent; + border-width: 0 0 var(--tab-border, 1px) 0; + border-start-start-radius: var(--tab-radius, 0.5rem); + border-start-end-radius: var(--tab-radius, 0.5rem); + border-bottom-color: var(--tab-border-color); + -webkit-padding-start: var(--tab-padding, 1rem); + padding-inline-start: var(--tab-padding, 1rem); + -webkit-padding-end: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); + padding-top: var(--tab-border, 1px); +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]), + .tabs-lifted .tab:is(input:checked){ + background-color: var(--tab-bg); + border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); + border-inline-start-color: var(--tab-border-color); + border-inline-end-color: var(--tab-border-color); + border-top-color: var(--tab-border-color); + -webkit-padding-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + -webkit-padding-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-bottom: var(--tab-border, 1px); + padding-top: 0; +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked):before{ + z-index: 1; + content: ""; + display: block; + position: absolute; + width: calc(100% + var(--tab-radius, 0.5rem) * 2); + height: var(--tab-radius, 0.5rem); + bottom: 0; + background-size: var(--tab-radius, 0.5rem); + background-position: top left, + top right; + background-repeat: no-repeat; + --tab-grad: calc(69% - var(--tab-border, 1px)); + --radius-start: radial-gradient( + circle at top left, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + --radius-end: radial-gradient( + circle at top right, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + background-image: var(--radius-start), var(--radius-end); +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted .tab:is(input:checked):first-child:before{ + background-image: var(--radius-end); + background-position: top right; +} + +[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):first-child:before{ + background-image: var(--radius-start); + background-position: top left; +} + +.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted .tab:is(input:checked):last-child:before{ + background-image: var(--radius-start); + background-position: top left; +} + +[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):last-child:before{ + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-lifted + .tab-active:not(.tab-disabled):not([disabled]) + + .tabs-lifted + .tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before{ + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-boxed .tab{ + border-radius: var(--rounded-btn, 0.5rem); +} + +.table tr.active, + .table tr.active:nth-child(even), + .table-zebra tbody tr:nth-child(even){ + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.table-zebra tr.active, + .table-zebra tr.active:nth-child(even), + .table-zebra-zebra tbody tr:nth-child(even){ + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); +} + +.textarea-bordered{ + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea:focus{ + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[disabled]{ + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder{ + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + @keyframes toast-pop{ 0%{ transform: scale(0.9); @@ -2067,6 +2376,36 @@ details.collapse summary{ place-items: end; } +.tabs-md :where(.tab){ + height: 2rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; +} + +.tabs-lg :where(.tab){ + height: 3rem; + font-size: 1.125rem; + line-height: 1.75rem; + line-height: 2; + --tab-padding: 1.25rem; +} + +.tabs-sm :where(.tab){ + height: 1.5rem; + font-size: 0.875rem; + line-height: .75rem; + --tab-padding: 0.75rem; +} + +.tabs-xs :where(.tab){ + height: 1.25rem; + font-size: 0.75rem; + line-height: .75rem; + --tab-padding: 0.5rem; +} + .avatar.online:before{ content: ""; position: absolute; @@ -2119,7 +2458,8 @@ details.collapse summary{ .join.join-horizontal > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-inline-start: -1px; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .modal-top :where(.modal-box){ @@ -2249,42 +2589,6 @@ details.collapse summary{ top: 1rem; } -.\!left-0{ - left: 0px !important; -} - -.left-20{ - left: 5rem; -} - -.right-20{ - right: 5rem; -} - -.-left-0{ - left: -0px; -} - -.-left-20{ - left: -5rem; -} - -.-left-24{ - left: -6rem; -} - -.-left-28{ - left: -7rem; -} - -.-left-\[115px\]{ - left: -115px; -} - -.-left-\[105px\]{ - left: -105px; -} - .-left-\[100px\]{ left: -100px; } @@ -2317,16 +2621,16 @@ details.collapse summary{ margin: 0px; } -.\!my-0{ - margin-top: 0px !important; - margin-bottom: 0px !important; -} - .my-0{ margin-top: 0px; margin-bottom: 0px; } +.\!my-0{ + margin-top: 0px !important; + margin-bottom: 0px !important; +} + .mb-2{ margin-bottom: 0.5rem; } @@ -2347,6 +2651,10 @@ details.collapse summary{ margin-top: 0.5rem; } +.mt-24{ + margin-top: 6rem; +} + .mt-4{ margin-top: 1rem; } @@ -2363,10 +2671,18 @@ details.collapse summary{ display: flex; } +.grid{ + display: grid; +} + .hidden{ display: none; } +.aspect-square{ + aspect-ratio: 1 / 1; +} + .h-3{ height: 0.75rem; } @@ -2396,20 +2712,20 @@ details.collapse summary{ min-height: 400px; } -.w-20{ - width: 5rem; -} - .w-3{ width: 0.75rem; } -.w-56{ - width: 14rem; +.w-32{ + width: 8rem; } -.w-96{ - width: 24rem; +.w-40{ + width: 10rem; +} + +.w-56{ + width: 14rem; } .w-\[24rem\]{ @@ -2420,6 +2736,10 @@ details.collapse summary{ width: 32rem; } +.w-\[550px\]{ + width: 550px; +} + .w-fit{ width: -moz-fit-content; width: fit-content; @@ -2429,6 +2749,30 @@ details.collapse summary{ width: 100%; } +.w-10{ + width: 2.5rem; +} + +.w-14{ + width: 3.5rem; +} + +.w-24{ + width: 6rem; +} + +.w-20{ + width: 5rem; +} + +.w-64{ + width: 16rem; +} + +.w-96{ + width: 24rem; +} + .min-w-\[300px\]{ min-width: 300px; } @@ -2437,6 +2781,10 @@ details.collapse summary{ max-width: 100%; } +.grow{ + flex-grow: 1; +} + .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2451,10 +2799,22 @@ details.collapse summary{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.cursor-pointer{ + cursor: pointer; +} + .resize-none{ resize: none; } +.grid-flow-col{ + grid-auto-flow: column; +} + +.grid-cols-2{ + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + .flex-row{ flex-direction: row; } @@ -2483,6 +2843,10 @@ details.collapse summary{ justify-content: space-around; } +.justify-stretch{ + justify-content: stretch; +} + .gap-2{ gap: 0.5rem; } @@ -2610,6 +2974,11 @@ details.collapse summary{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } +.bg-\[\#f6f6f6\]{ + --tw-bg-opacity: 1; + background-color: rgb(246 246 246 / var(--tw-bg-opacity)); +} + .bg-base-200{ --tw-bg-opacity: 1; background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); @@ -2837,6 +3206,22 @@ details.collapse summary{ outline-offset: 2px !important; } +.ring-1{ + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-black{ + --tw-ring-opacity: 1; + --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); +} + +.blur{ + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + .filter{ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -3047,7 +3432,17 @@ img[src] { display: flex; } + .sm\:w-10{ + width: 2.5rem; + } + .sm\:w-32{ width: 8rem; } } + +@media (min-width: 768px){ + .md\:w-full{ + width: 100%; + } +} From 8be57457c1c9c875810246d204a716c1e331c32b Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 17 Nov 2023 15:12:07 +0800 Subject: [PATCH 083/127] feat: update share avatar button --- js/AvatarPreview.js | 117 ++++++----- js/index.js | 7 +- js/tw-styles.css | 497 +++----------------------------------------- 3 files changed, 98 insertions(+), 523 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 5c28ba6..fa9b8c1 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -113,6 +113,7 @@ async function uploadImage() { } const jsonWorkflowLoading = van.state(true); +export const sharedAvatarLink = van.state(""); async function prepareImageFromUrlRedirect(stage) { await new Promise((resolve) => setTimeout(resolve, 2000)); @@ -130,8 +131,8 @@ async function prepareImageFromUrlRedirect(stage) { graph.change(); previewImg.val = api.apiURL( `/view?filename=${encodeURIComponent( - imageName, - )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}`, + imageName + )}&type=input&subfolder=create_avatar_endpoint${app.getPreviewFormatParam()}` ); console.log(previewImg); } @@ -176,7 +177,7 @@ export function AvatarPreview() { }); const loading = van.state(false); - const shareLoading = van.state(false); + const shareLoading = van.state("share"); // share, loading, shared api.addEventListener("execution_start", (evt) => { loading.val = true; @@ -207,14 +208,14 @@ export function AvatarPreview() { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-60% to-white text-transparent bg-clip-text font-gabarito text-4xl", }, - "Avatech v1", + "Avatech v1" ), div( { class: () => " bg-gradient-to-b from-black via-[#5F5F5F] via-50% to-white text-transparent bg-clip-text font-gabarito text-2xl", }, - "Get your DALLE3 AI Personal Clone", + "Get your DALLE3 AI Personal Clone" ), div( { @@ -305,7 +306,7 @@ export function AvatarPreview() { }, }, div({ class: "badge badge-neutral" }, "2"), - "Edit Segment", + "Edit Segment" ), button( { @@ -513,7 +514,7 @@ export function AvatarPreview() { class: () => "w-full flex justify-center font-bold italic text-gray-500", }, - span("We are launching OpenAI Assistant API integration soon!"), + span("We are launching OpenAI Assistant API integration soon!") ), div( { class: () => "w-[24rem] flex justify-center items-center" }, @@ -529,34 +530,52 @@ export function AvatarPreview() { button( { class: () => - "btn rounded rounded-l-none rounded-r-md no-animation bg-neutral hover:bg-neutral-focus text-white border-none normal-case", + "btn rounded rounded-l-none rounded-r-md no-animation bg-neutral-800 hover:bg-neutral-950 text-white border-none normal-case", onclick: async () => { - shareLoading.val = true; - const url = await (await fetch("./get_webhook")).json(); - await uploadPreview(); - await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: "Avabot", - avatar_url: - "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", - content: "New register! \n" + email.val, - }), - }); - shareLoading.val = false; + if (shareLoading.val === "share") { + shareLoading.val = "loading"; + const url = await (await fetch("./get_webhook")).json(); + await uploadPreview(); + await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username: "Avabot", + avatar_url: + "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", + content: "New register! \n" + email.val, + }), + }); + shareLoading.val = "shared"; + } + if (sharedAvatarLink.val) { + await navigator.clipboard.writeText(sharedAvatarLink.val); + alertDialog.val = { + text: "Avatar link copied to clipboard!", + time: 5000, + }; + } }, }, - () => - shareLoading.val - ? span({ - class: "loading loading-spinner loading-md", - }) - : "Get Avatar Link", - ), - ), + () => { + switch (shareLoading.val) { + case "share": + return "Get Avatar Link"; + case "loading": + return span({ + class: "loading loading-spinner loading-md", + }); + case "shared": + return span({ + class: "iconify text-xl", + "data-icon": "lucide:copy-check", + }); + } + } + ) + ) ); }; @@ -573,7 +592,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "ic:round-close", "data-inline": "false", - }), + }) ); }; @@ -590,7 +609,7 @@ export function AvatarPreview() { class: "iconify text-lg", "data-icon": "mdi:restart", "data-inline": "false", - }), + }) ); }; @@ -612,8 +631,8 @@ export function AvatarPreview() { "data-inline": "false", }), span({ class: "sm:flex hidden" }, () => - jsonWorkflowLoading.val ? "Loading" : "Change workflow", - ), + jsonWorkflowLoading.val ? "Loading" : "Change workflow" + ) ), ul( { @@ -635,7 +654,7 @@ export function AvatarPreview() { else isGenerateFlow.val = true; }, }, - () => val, + () => val ); }), div({ class: () => "divider !my-0" }), @@ -661,9 +680,9 @@ export function AvatarPreview() { // document.getElementById("comfy-load-button").click(); }, }, - "Import...", - ), - ), + "Import..." + ) + ) ); // return button( // { @@ -706,7 +725,7 @@ export function AvatarPreview() { "absolute top-4 right-4 btn sm:w-32 w-20 text-black btn-ghost text-xs z-[200] !px-0 normal-case sm:btn-md btn-sm", onclick: () => window.open("https://twitter.com/avatech_gg", "_blank"), }, - "Twitter", + "Twitter" ); }; @@ -733,7 +752,7 @@ export function AvatarPreview() { }, renderCloseButton(), renderRestartButton(), - renderChangeWorkflowButton(), + renderChangeWorkflowButton() ), renderTwitter(), () => { @@ -746,7 +765,7 @@ export function AvatarPreview() { }, renderIFrame(), renderSteps(), - renderShareLink(), + renderShareLink() ); } else { return div( @@ -759,12 +778,12 @@ export function AvatarPreview() { div( { class: () => "flex flex-col" }, renderIFrame(), - renderShareLink(), - ), + renderShareLink() + ) ); } - }, - ), + } + ) ); } @@ -784,8 +803,8 @@ function showImage(name) { } img.src = api.apiURL( `/view?filename=${encodeURIComponent( - name, - )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`, + name + )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}` ); node.setSizeForImage?.(); } diff --git a/js/index.js b/js/index.js index 527a1b8..878826c 100644 --- a/js/index.js +++ b/js/index.js @@ -26,6 +26,7 @@ import { initModel, loadNpyTensor } from "./onnx.js"; import "https://code.iconify.design/3/3.1.0/iconify.min.js"; import { autoSegment, drawSegment, getClicks } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; +import { sharedAvatarLink } from "./AvatarPreview.js"; export const generatedImages = {}; @@ -713,11 +714,9 @@ export async function uploadPreview() { body: file, }).catch((error) => console.error(error)); - infoDialog.show( - `Preview avatar url: https://editor.avatech.ai/viewer?avatarId=${labData.modelId}` + - `\nChat url: https://labs.avatech.ai?avatarId=${labData.modelId}` - ); + sharedAvatarLink.val = `https://editor.avatech.ai/viewer?avatarId=${labData?.modelId}`; previewModelId.val = labData.modelId; + return labData; } } diff --git a/js/tw-styles.css b/js/tw-styles.css index 9d4d538..4339fc8 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -726,10 +726,6 @@ html{ --tw-text-opacity: 1; color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); } - - .tab:hover{ - --tw-text-opacity: 1; - } } .btn{ @@ -1033,75 +1029,12 @@ html{ background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); } } - - .tab[disabled], - .tab[disabled]:hover{ - cursor: not-allowed; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; - } } .dropdown:is(details) summary::-webkit-details-marker{ display: none; } -.file-input{ - height: 3rem; - flex-shrink: 1; - -webkit-padding-end: 1rem; - padding-inline-end: 1rem; - font-size: 1rem; - line-height: 2; - line-height: 1.5rem; - overflow: hidden; - border-radius: var(--rounded-btn, 0.5rem); - border-width: 1px; - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0; - --tw-bg-opacity: 1; - background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); -} - -.file-input::file-selector-button{ - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; - display: inline-flex; - height: 100%; - flex-shrink: 0; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - padding-left: 1rem; - padding-right: 1rem; - text-align: center; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 1em; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 200ms; - border-style: solid; - --tw-border-opacity: 1; - border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - font-weight: 600; - text-transform: uppercase; - --tw-text-opacity: 1; - color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); - text-decoration-line: none; - border-width: var(--border-btn, 1px); - animation: button-pop var(--animation-btn, 0.25s) ease-out; -} - .label{ display: flex; -webkit-user-select: none; @@ -1336,92 +1269,6 @@ html{ overflow: hidden; } -.radio{ - flex-shrink: 0; - --chkbg: var(--bc); - height: 1.5rem; - width: 1.5rem; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border-radius: 9999px; - border-width: 1px; - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0.2; -} - -.tabs{ - display: grid; - align-items: flex-end; -} - -.tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(.tab-active), - .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(.tab-active){ - border-bottom-color: transparent; -} - -.tab{ - position: relative; - grid-row-start: 1; - display: inline-flex; - height: 2rem; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - text-align: center; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - --tab-padding: 1rem; - --tw-text-opacity: 0.5; - --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); - --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); - --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); - color: var(--tab-color); - -webkit-padding-start: var(--tab-padding, 1rem); - padding-inline-start: var(--tab-padding, 1rem); - -webkit-padding-end: var(--tab-padding, 1rem); - padding-inline-end: var(--tab-padding, 1rem); -} - -.tab:is(input[type="radio"]):after{ - --tw-content: attr(aria-label); - content: var(--tw-content); -} - -.tab:not(input):empty{ - cursor: default; - grid-column-start: span 9999; -} - -.tab-content{ - grid-column: 1 / -1; - grid-column-end: span 9999; - grid-row-start: 2; - margin-top: calc(var(--tab-border) * -1); - display: none; - border-color: transparent; - border-width: var(--tab-border, 0); -} - -:checked + .tab-content:nth-child(2), - .tab-active + .tab-content:nth-child(2){ - border-start-start-radius: 0px; -} - -input.tab:checked + .tab-content, -.tab-active + .tab-content{ - display: block; -} - .textarea{ min-height: 3rem; flex-shrink: 1; @@ -1684,46 +1531,6 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.file-input-bordered{ - --tw-border-opacity: 0.2; -} - -.file-input:focus{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); -} - -.file-input-disabled, - .file-input[disabled]{ - cursor: not-allowed; - --tw-border-opacity: 1; - border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); - --tw-text-opacity: 0.2; -} - -.file-input-disabled::-moz-placeholder, .file-input[disabled]::-moz-placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.file-input-disabled::placeholder, - .file-input[disabled]::placeholder{ - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); - --tw-placeholder-opacity: 0.2; -} - -.file-input-disabled::file-selector-button, .file-input[disabled]::file-selector-button{ - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - .input input:focus{ outline: 2px solid transparent; outline-offset: 2px; @@ -1985,32 +1792,6 @@ details.collapse summary::-webkit-details-marker{ } } -.radio:focus{ - box-shadow: none; -} - -.radio:focus-visible{ - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); -} - -.radio:checked, - .radio[aria-checked="true"]{ - --tw-bg-opacity: 1; - background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); - background-image: none; - animation: radiomark var(--animation-input, 0.2s) ease-out; - box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, - 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; -} - -.radio:disabled{ - cursor: not-allowed; - opacity: 0.2; -} - @keyframes radiomark{ 0%{ box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, @@ -2052,132 +1833,6 @@ details.collapse summary::-webkit-details-marker{ } } -.tabs-lifted .tab:focus-visible{ - border-end-end-radius: 0; - border-end-start-radius: 0; -} - -.tab.tab-active:not(.tab-disabled):not([disabled]), - .tab:is(input:checked){ - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 1; - --tw-text-opacity: 1; -} - -.tab:focus{ - outline: 2px solid transparent; - outline-offset: 2px; -} - -.tab:focus-visible{ - outline: 2px solid currentColor; - outline-offset: -5px; -} - -.tab-disabled, - .tab[disabled]{ - cursor: not-allowed; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - -.tabs-bordered .tab{ - border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); - --tw-border-opacity: 0.2; - border-style: solid; - border-bottom-width: calc(var(--tab-border, 1px) + 1px); -} - -.tabs-lifted .tab{ - border: var(--tab-border, 1px) solid transparent; - border-width: 0 0 var(--tab-border, 1px) 0; - border-start-start-radius: var(--tab-radius, 0.5rem); - border-start-end-radius: var(--tab-radius, 0.5rem); - border-bottom-color: var(--tab-border-color); - -webkit-padding-start: var(--tab-padding, 1rem); - padding-inline-start: var(--tab-padding, 1rem); - -webkit-padding-end: var(--tab-padding, 1rem); - padding-inline-end: var(--tab-padding, 1rem); - padding-top: var(--tab-border, 1px); -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]), - .tabs-lifted .tab:is(input:checked){ - background-color: var(--tab-bg); - border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); - border-inline-start-color: var(--tab-border-color); - border-inline-end-color: var(--tab-border-color); - border-top-color: var(--tab-border-color); - -webkit-padding-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - -webkit-padding-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); - padding-bottom: var(--tab-border, 1px); - padding-top: 0; -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked):before{ - z-index: 1; - content: ""; - display: block; - position: absolute; - width: calc(100% + var(--tab-radius, 0.5rem) * 2); - height: var(--tab-radius, 0.5rem); - bottom: 0; - background-size: var(--tab-radius, 0.5rem); - background-position: top left, - top right; - background-repeat: no-repeat; - --tab-grad: calc(69% - var(--tab-border, 1px)); - --radius-start: radial-gradient( - circle at top left, - transparent var(--tab-grad), - var(--tab-border-color) calc(var(--tab-grad) + 0.25px), - var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), - var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) - ); - --radius-end: radial-gradient( - circle at top right, - transparent var(--tab-grad), - var(--tab-border-color) calc(var(--tab-grad) + 0.25px), - var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), - var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) - ); - background-image: var(--radius-start), var(--radius-end); -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted .tab:is(input:checked):first-child:before{ - background-image: var(--radius-end); - background-position: top right; -} - -[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):first-child:before{ - background-image: var(--radius-start); - background-position: top left; -} - -.tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted .tab:is(input:checked):last-child:before{ - background-image: var(--radius-start); - background-position: top left; -} - -[dir="rtl"] .tabs-lifted .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted .tab:is(input:checked):last-child:before{ - background-image: var(--radius-end); - background-position: top right; -} - -.tabs-lifted - .tab-active:not(.tab-disabled):not([disabled]) - + .tabs-lifted - .tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before{ - background-image: var(--radius-end); - background-position: top right; -} - -.tabs-boxed .tab{ - border-radius: var(--rounded-btn, 0.5rem); -} - .table tr.active, .table tr.active:nth-child(even), .table-zebra tbody tr:nth-child(even){ @@ -2376,36 +2031,6 @@ details.collapse summary::-webkit-details-marker{ place-items: end; } -.tabs-md :where(.tab){ - height: 2rem; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - --tab-padding: 1rem; -} - -.tabs-lg :where(.tab){ - height: 3rem; - font-size: 1.125rem; - line-height: 1.75rem; - line-height: 2; - --tab-padding: 1.25rem; -} - -.tabs-sm :where(.tab){ - height: 1.5rem; - font-size: 0.875rem; - line-height: .75rem; - --tab-padding: 0.75rem; -} - -.tabs-xs :where(.tab){ - height: 1.25rem; - font-size: 0.75rem; - line-height: .75rem; - --tab-padding: 0.5rem; -} - .avatar.online:before{ content: ""; position: absolute; @@ -2529,6 +2154,10 @@ details.collapse summary::-webkit-details-marker{ position: relative; } +.-left-\[100px\]{ + left: -100px; +} + .bottom-0{ bottom: 0px; } @@ -2589,10 +2218,6 @@ details.collapse summary::-webkit-details-marker{ top: 1rem; } -.-left-\[100px\]{ - left: -100px; -} - .z-\[1000\]{ z-index: 1000; } @@ -2621,16 +2246,16 @@ details.collapse summary::-webkit-details-marker{ margin: 0px; } -.my-0{ - margin-top: 0px; - margin-bottom: 0px; -} - .\!my-0{ margin-top: 0px !important; margin-bottom: 0px !important; } +.my-0{ + margin-top: 0px; + margin-bottom: 0px; +} + .mb-2{ margin-bottom: 0.5rem; } @@ -2651,10 +2276,6 @@ details.collapse summary::-webkit-details-marker{ margin-top: 0.5rem; } -.mt-24{ - margin-top: 6rem; -} - .mt-4{ margin-top: 1rem; } @@ -2679,10 +2300,6 @@ details.collapse summary::-webkit-details-marker{ display: none; } -.aspect-square{ - aspect-ratio: 1 / 1; -} - .h-3{ height: 0.75rem; } @@ -2712,22 +2329,22 @@ details.collapse summary::-webkit-details-marker{ min-height: 400px; } +.w-20{ + width: 5rem; +} + .w-3{ width: 0.75rem; } -.w-32{ - width: 8rem; -} - -.w-40{ - width: 10rem; -} - .w-56{ width: 14rem; } +.w-96{ + width: 24rem; +} + .w-\[24rem\]{ width: 24rem; } @@ -2736,10 +2353,6 @@ details.collapse summary::-webkit-details-marker{ width: 32rem; } -.w-\[550px\]{ - width: 550px; -} - .w-fit{ width: -moz-fit-content; width: fit-content; @@ -2749,30 +2362,6 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.w-10{ - width: 2.5rem; -} - -.w-14{ - width: 3.5rem; -} - -.w-24{ - width: 6rem; -} - -.w-20{ - width: 5rem; -} - -.w-64{ - width: 16rem; -} - -.w-96{ - width: 24rem; -} - .min-w-\[300px\]{ min-width: 300px; } @@ -2781,10 +2370,6 @@ details.collapse summary::-webkit-details-marker{ max-width: 100%; } -.grow{ - flex-grow: 1; -} - .-translate-x-1\/2{ --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2799,18 +2384,10 @@ details.collapse summary::-webkit-details-marker{ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.cursor-pointer{ - cursor: pointer; -} - .resize-none{ resize: none; } -.grid-flow-col{ - grid-auto-flow: column; -} - .grid-cols-2{ grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -2843,10 +2420,6 @@ details.collapse summary::-webkit-details-marker{ justify-content: space-around; } -.justify-stretch{ - justify-content: stretch; -} - .gap-2{ gap: 0.5rem; } @@ -2974,11 +2547,6 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } -.bg-\[\#f6f6f6\]{ - --tw-bg-opacity: 1; - background-color: rgb(246 246 246 / var(--tw-bg-opacity)); -} - .bg-base-200{ --tw-bg-opacity: 1; background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); @@ -3004,6 +2572,11 @@ details.collapse summary::-webkit-details-marker{ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); } +.bg-neutral-800{ + --tw-bg-opacity: 1; + background-color: rgb(38 38 38 / var(--tw-bg-opacity)); +} + .bg-orange-100{ --tw-bg-opacity: 1; background-color: rgb(255 237 213 / var(--tw-bg-opacity)); @@ -3206,17 +2779,6 @@ details.collapse summary::-webkit-details-marker{ outline-offset: 2px !important; } -.ring-1{ - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -.ring-black{ - --tw-ring-opacity: 1; - --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity)); -} - .blur{ --tw-blur: blur(8px); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); @@ -3417,6 +2979,11 @@ img[src] { cursor: pointer; } +.hover\:bg-neutral-950:hover{ + --tw-bg-opacity: 1; + background-color: rgb(10 10 10 / var(--tw-bg-opacity)); +} + .hover\:text-red-500:hover{ --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); @@ -3432,17 +2999,7 @@ img[src] { display: flex; } - .sm\:w-10{ - width: 2.5rem; - } - .sm\:w-32{ width: 8rem; } } - -@media (min-width: 768px){ - .md\:w-full{ - width: 100%; - } -} From 13b94fa25baaa7573d85f27caefa8ab488d21087 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 17 Nov 2023 15:32:56 +0800 Subject: [PATCH 084/127] feat: add alert colors --- js/Alert.js | 20 ++++++++++++++++++-- js/AvatarPreview.js | 1 + js/tw-styles.css | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/js/Alert.js b/js/Alert.js index 2dab430..a4c3f0d 100644 --- a/js/Alert.js +++ b/js/Alert.js @@ -3,8 +3,25 @@ import { van } from "./van.js"; const { div, span } = van.tags; export function Alert() { + const color = van.state("bg-orange-100 text-orange-700 border-orange-500"); + van.derive(() => { if (alertDialog.val.time > 0) { + switch (alertDialog.val.type) { + case "error": + color.val = "bg-red-100 text-red-700 border-red-500"; + break; + case "success": + color.val = "bg-green-100 text-green-700 border-green-500"; + break; + case "info": + color.val = "bg-blue-100 text-blue-700 border-blue-500"; + break; + case "warning": + default: + color.val = "bg-orange-100 text-orange-700 border-orange-500"; + break; + } setTimeout(() => { alertDialog.val = { text: "", time: 0 }; }, alertDialog.val.time); @@ -19,8 +36,7 @@ export function Alert() { }, div( { - class: - "bg-orange-100 border-t-4 border-orange-500 rounded-sm text-orange-700 p-2", + class: () => `${color.val} border-t-4 rounded-sm p-2`, }, () => span(alertDialog.val.text) ) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index fa9b8c1..f953c8f 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -554,6 +554,7 @@ export function AvatarPreview() { await navigator.clipboard.writeText(sharedAvatarLink.val); alertDialog.val = { text: "Avatar link copied to clipboard!", + type: "success", time: 5000, }; } diff --git a/js/tw-styles.css b/js/tw-styles.css index 4339fc8..ee3d6e9 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2532,11 +2532,21 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(0 0 0 / var(--tw-border-opacity)); } +.border-blue-500{ + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + .border-gray-300{ --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-green-500{ + --tw-border-opacity: 1; + border-color: rgb(34 197 94 / var(--tw-border-opacity)); +} + .border-orange-500{ --tw-border-opacity: 1; border-color: rgb(249 115 22 / var(--tw-border-opacity)); @@ -2547,6 +2557,11 @@ details.collapse summary::-webkit-details-marker{ border-color: rgb(168 85 247 / var(--tw-border-opacity)); } +.border-red-500{ + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + .bg-base-200{ --tw-bg-opacity: 1; background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); @@ -2557,11 +2572,21 @@ details.collapse summary::-webkit-details-marker{ background-color: rgb(0 0 0 / var(--tw-bg-opacity)); } +.bg-blue-100{ + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + .bg-gray-900{ --tw-bg-opacity: 1; background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.bg-green-100{ + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); +} + .bg-green-500{ --tw-bg-opacity: 1; background-color: rgb(34 197 94 / var(--tw-bg-opacity)); @@ -2582,6 +2607,11 @@ details.collapse summary::-webkit-details-marker{ background-color: rgb(255 237 213 / var(--tw-bg-opacity)); } +.bg-red-100{ + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} + .bg-red-500{ --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity)); @@ -2735,6 +2765,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(0 0 0 / var(--tw-text-opacity)); } +.text-blue-700{ + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + .text-gray-300{ --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); @@ -2745,6 +2780,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(107 114 128 / var(--tw-text-opacity)); } +.text-green-700{ + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity)); +} + .text-orange-700{ --tw-text-opacity: 1; color: rgb(194 65 12 / var(--tw-text-opacity)); @@ -2755,6 +2795,11 @@ details.collapse summary::-webkit-details-marker{ color: rgb(168 85 247 / var(--tw-text-opacity)); } +.text-red-700{ + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity)); +} + .text-transparent{ color: transparent; } From b6259416a24b9df85edbe58ca0da745166b50c17 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 17 Nov 2023 17:08:21 +0800 Subject: [PATCH 085/127] feat: remove background option --- js/LayerEditor.js | 33 +++++++++++++++++++++++++++++++++ js/index.js | 10 ++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 74a4cf3..72df824 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -1,5 +1,6 @@ import { SideBar } from "./SideBar.js"; import { api } from "./api.js"; +import { app } from "./app.js"; import { runONNX } from "./onnx.js"; import { showImageEditor, @@ -22,6 +23,7 @@ const { button, div, img, canvas, span } = van.tags; let throttle = false; const positivePrompt = van.state(true); +const enableBackgroundRemover = van.state(true); const isMobileDevice = () => { return window.screen.width < 768; }; @@ -228,6 +230,23 @@ export async function autoSegment() { console.log("Done"); } +export function setRemoveBackgroundNode() { + const rmBgNodes = app.graph.findNodesByType( + "Image Rembg (Remove Background)" + ); + if (!rmBgNodes?.length) { + alertDialog.val = { + text: "Remove background node not found. Please ensure the workflow is correct.", + time: 5000, + }; + return; + } + rmBgNodes.forEach((node) => { + // node is bypassed if mode is 4 + node.mode = enableBackgroundRemover.val ? 0 : 4; + }); +} + export function updateImagePrompts() { if (selectedLayer.val !== "" && selectedLayer.val !== undefined) { imagePromptsMulti.val = { @@ -412,6 +431,20 @@ export function LayerEditor() { }, div(() => (showSidebar.val ? "Hide UI" : "Show UI")) ), + button( + { + class: () => + "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit", + onclick: () => { + enableBackgroundRemover.val = !enableBackgroundRemover.val; + setRemoveBackgroundNode(); + }, + }, + () => + enableBackgroundRemover.val + ? "Background Remover On" + : "Background Remover Off" + ), button( { class: () => diff --git a/js/index.js b/js/index.js index 878826c..90bc25b 100644 --- a/js/index.js +++ b/js/index.js @@ -16,7 +16,7 @@ import { shareLoading, previewModelId, embeddingID, - enableAutoSegment + enableAutoSegment, } from "./state.js"; import { van } from "./van.js"; import { app } from "./app.js"; @@ -240,7 +240,10 @@ function getInputWidgetValue(node, inputIndex, widgetName) { /** @type {LGraphNode} */ let nodea = graph._nodes_by_id[targetLink.origin_id]; - while (nodea.type == "Reroute") { + while ( + nodea.type === "Reroute" || + nodea.type === "Image Rembg (Remove Background)" + ) { nodea = nodea.getInputNode(0); } @@ -346,8 +349,7 @@ function showMyImageEditor(node) { ); loadNpyTensor(embeedingUrl).then(async (tensor) => { embeddings.val = tensor; - if (enableAutoSegment.val) - await autoSegment(); + if (enableAutoSegment.val) await autoSegment(); drawSegment(getClicks()); }); targetNode.val = node; From 907ba0285c683463e971eb61e5a64bdfca206a29 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 17 Nov 2023 05:50:17 -0500 Subject: [PATCH 086/127] fix: viewer --- js/AvatarPreview.js | 2 +- js/state.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index f953c8f..2a531d8 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -495,7 +495,7 @@ export function AvatarPreview() { name: "avatech-viewer-iframe", allow: "cross-origin-isolated", class: () => - "w-full h-full min-w-[300px] min-h-[400px] z-[100] pointer-events-auto flex border-none overflow-hidden" + + "w-full h-full min-w-[400px] min-h-[400px] z-[100] pointer-events-auto flex border-none overflow-hidden" + (showPreview.val ? "" : "hidden"), // src: "https://labs.avatech.ai/viewer/default", // src: "http://localhost:3000/viewer/default", diff --git a/js/state.js b/js/state.js index 5819c73..a5016d7 100644 --- a/js/state.js +++ b/js/state.js @@ -18,7 +18,7 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true"); +export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true&hideUI=true"); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); export const enableAutoSegment = van.state(false); From 6a8a2853db75f9b90059023a280e2edba9ff93d9 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 17 Nov 2023 20:03:25 +0800 Subject: [PATCH 087/127] fix: auto seg overwrite manually edited points --- js/AvatarPreview.js | 80 ++++++++++++++------------------------------- js/LayerEditor.js | 3 ++ js/index.js | 4 +-- js/tw-styles.css | 12 ++----- 4 files changed, 31 insertions(+), 68 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 2a531d8..37c71ec 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -15,7 +15,7 @@ const { button, iframe, div, img, input, label, span, textarea, ul, li } = import { app } from "./app.js"; import { uploadPreview } from "./index.js"; import { api } from "./api.js"; -import { uploadSegments } from "./LayerEditor.js"; +import { segmented, uploadSegments } from "./LayerEditor.js"; import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; @@ -283,6 +283,9 @@ export function AvatarPreview() { const image = img({ class: () => "z-[10] object-contain w-full h-[394px] border", src: previewImg, + onload: () => { + segmented.val = false; + } }); if (isMobileDevice()) { @@ -291,36 +294,18 @@ export function AvatarPreview() { return previewImg.val === "" ? dnd : image; } }, - div( + button( { - class: "grid grid-cols-2 gap-2 w-full", + class: () => + "btn w-full normal-case " + + (stage.val < 1 ? "btn-disabled" : ""), + onclick: () => { + enableAutoSegment.val = true; + editSegment(stage) + }, }, - button( - { - class: () => - "btn normal-case " + - (stage.val < 1 ? "btn-disabled" : ""), - onclick: () => { - enableAutoSegment.val = false; - editSegment(stage) - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit Segment" - ), - button( - { - class: () => - "btn normal-case " + - (stage.val < 1 ? "btn-disabled" : ""), - onclick: () => { - enableAutoSegment.val = true; - editSegment(stage) - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit Segment (Auto)", - ), + div({ class: "badge badge-neutral" }, "2"), + "Edit Segment", ), button( { @@ -436,35 +421,18 @@ export function AvatarPreview() { ? span({ class: "loading loading-spinner loading-md" }) : "Make It Alive!", ), - div( + button( { - class: "grid grid-cols-2 gap-2 w-full" + class: () => + "btn w-full normal-case ", + onclick: () => { + enableAutoSegment.val = false; + editSegment(stage) + }, }, - button( - { - class: () => - "btn normal-case ", - onclick: () => { - enableAutoSegment.val = false; - editSegment(stage) - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit Segment", - ), - button( - { - class: () => - "btn normal-case ", - onclick: () => { - enableAutoSegment.val = true; - editSegment(stage) - }, - }, - div({ class: "badge badge-neutral" }, "2"), - "Edit Segment (Auto)", - ), - ) + div({ class: "badge badge-neutral" }, "2"), + "Edit Segment", + ), // button( // { // class: "btn w-full normal-case", diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 72df824..e6d2951 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -114,6 +114,8 @@ const layerMapping = { }, }; +export const segmented = van.state(false); + export async function autoSegment() { const image = document.getElementById("image"); const landmarks = faceLandmarker.detect(image).faceLandmarks[0]; @@ -227,6 +229,7 @@ export async function autoSegment() { } }); imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; + segmented.val = true; console.log("Done"); } diff --git a/js/index.js b/js/index.js index 90bc25b..8cef05b 100644 --- a/js/index.js +++ b/js/index.js @@ -24,7 +24,7 @@ import { api } from "./api.js"; import { Container } from "./Container.js"; import { initModel, loadNpyTensor } from "./onnx.js"; import "https://code.iconify.design/3/3.1.0/iconify.min.js"; -import { autoSegment, drawSegment, getClicks } from "./LayerEditor.js"; +import { autoSegment, drawSegment, getClicks, segmented } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; import { sharedAvatarLink } from "./AvatarPreview.js"; @@ -349,7 +349,7 @@ function showMyImageEditor(node) { ); loadNpyTensor(embeedingUrl).then(async (tensor) => { embeddings.val = tensor; - if (enableAutoSegment.val) await autoSegment(); + if (enableAutoSegment.val && !segmented.val) await autoSegment(); drawSegment(getClicks()); }); targetNode.val = node; diff --git a/js/tw-styles.css b/js/tw-styles.css index ee3d6e9..d343eef 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2292,10 +2292,6 @@ details.collapse summary::-webkit-details-marker{ display: flex; } -.grid{ - display: grid; -} - .hidden{ display: none; } @@ -2362,8 +2358,8 @@ details.collapse summary::-webkit-details-marker{ width: 100%; } -.min-w-\[300px\]{ - min-width: 300px; +.min-w-\[400px\]{ + min-width: 400px; } .max-w-\[100\%\]{ @@ -2388,10 +2384,6 @@ details.collapse summary::-webkit-details-marker{ resize: none; } -.grid-cols-2{ - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - .flex-row{ flex-direction: row; } From d55f9be3381ca89a2b05002c689d18142cabec02 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Mon, 20 Nov 2023 06:31:18 -0500 Subject: [PATCH 088/127] fix: autosegment didn't save --- js/AvatarPreview.js | 2 + js/LayerEditor.js | 12 +- js/SideBar.js | 386 ++++++++++++++++++++++++-------------------- js/index.js | 2 + js/state.js | 3 + js/tw-styles.css | 4 + 6 files changed, 232 insertions(+), 177 deletions(-) diff --git a/js/AvatarPreview.js b/js/AvatarPreview.js index 37c71ec..d0a0f93 100644 --- a/js/AvatarPreview.js +++ b/js/AvatarPreview.js @@ -20,6 +20,7 @@ import { initModel } from "./onnx.js"; // import { uploadSegments } from "./LayerEditor.js"; const workflowList = [ + "idle_avatar_(trigger)", "Auto_segment_workflow", "BronyaZaychik_(ChinaDress)", "BronyaZaychik_(Default_Silverwing)", @@ -28,6 +29,7 @@ const workflowList = [ "BronyaZaychikLora_withhand", "SilverWolf_(Default)", "SilverWolf_(Maid)", + "SilverWolfLora_withhand", ]; function editSegment(stage) { diff --git a/js/LayerEditor.js b/js/LayerEditor.js index e6d2951..09809b9 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -15,6 +15,7 @@ import { embeddings, embeddingID, alertDialog, + allImagePrompts, } from "./state.js"; import { van } from "./van.js"; import vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3"; @@ -410,6 +411,15 @@ export function LayerEditor() { console.log("close"); showImageEditor.val = false; await uploadSegments(); + + // allImagePrompts.val = [ + // ...allImagePrompts.val, + // { + // version: "v" + allImagePrompts.val.length, + // prompt: imagePromptsMulti.val, + // }, + // ]; + // api.fetchApi("/segments_order", { // method: "POST", // body: JSON.stringify({ @@ -461,7 +471,7 @@ export function LayerEditor() { div( { class: - "hidden w-full flex justify-center absolute top-0 left-0 right-0 items-center", + "hidden w-full justify-center absolute top-0 left-0 right-0 items-center", }, button( { diff --git a/js/SideBar.js b/js/SideBar.js index ec5836a..44d21d2 100644 --- a/js/SideBar.js +++ b/js/SideBar.js @@ -5,6 +5,7 @@ import { imagePromptsMulti, targetNode, showImageEditor, + allImagePrompts, } from "./state.js"; import { van } from "./van.js"; const { @@ -61,196 +62,229 @@ export function SideBar() { const layer_to_delete = van.state(""); return div( - { - class: - "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 left-0 bottom-0 items-start gap-2", - }, - () => { - const layers = Object.entries(imagePromptsMulti.val); - return ul( - { - class: "menu bg-base-200 w-56 rounded-box text-base-content ", - }, - button({ - onclick: () => { - const layers = Object.entries(imagePromptsMulti.val); - layers.map(([key, value]) => { - imagePrompts.val = []; - imagePromptsMulti.val[key] = []; - }); - drawSegment([]); - updateImagePrompts(); + div( + { + class: + "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 left-0 bottom-0 items-start gap-2", + }, + () => { + const layers = Object.entries(imagePromptsMulti.val); + return ul( + { + class: "menu bg-base-200 w-56 rounded-box text-base-content ", }, - class: "btn btn-ghost normal-case flex", - }, "Clear ALL"), - layers.length === 0 ? li(a("Empty layer")) : null, - ...layers.map(([key, value]) => { - return li( - a( - { - class: () => - `normal-case text-start items-start flex items-center justify-between ${ - selectedLayer.val === key ? "active" : "" - }`, - onclick: () => { - selectedLayer.val = key; - imagePrompts.val = imagePromptsMulti.val[key]; - drawSegment(getClicks()); - }, - }, - key, - div( - {}, - button( - { - class: - "btn btn-circle btn-xs btn-ghost group hover:text-red-500", - onclick: (e) => { - console.log("clear"); - e.preventDefault(); - e.stopPropagation(); - imagePrompts.val = []; - imagePromptsMulti.val[key] = []; - drawSegment([]); - updateImagePrompts(); - }, - }, - span({ - class: "iconify", - "data-icon": "ant-design:clear-outlined", - "data-inline": "false", - }) - ), - button( - { - class: - "btn btn-circle btn-xs btn-ghost group hover:text-red-500", - onclick: (e) => { - console.log("delete"); - e.preventDefault(); - e.stopPropagation(); - layer_to_delete.val = key; - setTimeout(() => { - delete_layer_dialog.showModal(); - }, 0); - }, - }, - span({ - class: "iconify", - "data-icon": "ic:baseline-delete", - "data-inline": "false", - }) - ) - ) - ) - ); - }), - div({ class: "divider !py-0 my-0" }), - li( - a( + button( { - class: "flex items-center justify-between", onclick: () => { - my_modal_3.showModal(); - }, - }, - "New Layer", - span({ - class: "iconify", - "data-icon": "ic:outline-plus", - "data-inline": "false", - }) - ) - ) - ); - }, - () => - ConfirmDialog( - { - id: "delete_layer_dialog", - title: "Delete Layer: " + layer_to_delete.val, - onsubmit: () => { - imagePromptsMulti.val = Object.fromEntries( - Object.entries(imagePromptsMulti.val).filter( - ([key, value]) => key !== layer_to_delete.val - ) - ); - console.log(imagePromptsMulti.val); - if (selectedLayer.val === layer_to_delete.val) { - // Select another layer if there is one - if (Object.keys(imagePromptsMulti.val).length > 0) { - selectedLayer.val = Object.keys(imagePromptsMulti.val)[0]; - imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; - } else { - selectedLayer.val = ""; - imagePrompts.val = []; - } - } - targetNode.val.graph.change(); - updateImagePrompts(); - delete_layer_dialog.close(); - }, - }, - p("Are you sure you want to delete this layer?") - ), - () => - dialog( - { id: "my_modal_3", class: "modal" }, - div( - { class: "modal-box text-base-content" }, - form( - { - class: "gap-2 flex flex-col", - method: "dialog", - onsubmit: (e) => { - console.log("add new layer"); - e.preventDefault(); - const inputText = e.target.elements[1].value; - imagePromptsMulti.val = { - ...imagePromptsMulti.val, - [inputText]: [], - }; - console.log(inputText, imagePromptsMulti.val); - my_modal_3.close(); - e.target.elements[1].value = ""; - - selectedLayer.val = inputText; - imagePrompts.val = imagePromptsMulti.val[inputText]; - drawSegment(getClicks()); + layers.map(([key, value]) => { + imagePrompts.val = []; + imagePromptsMulti.val[key] = []; + }); + drawSegment([]); updateImagePrompts(); }, + class: "btn btn-ghost normal-case flex", }, - button( + "Clear ALL" + ), + layers.length === 0 ? li(a("Empty layer")) : null, + ...layers.map(([key, value]) => { + return li( + a( + { + class: () => + `normal-case text-start items-start flex items-center justify-between ${ + selectedLayer.val === key ? "active" : "" + }`, + onclick: () => { + selectedLayer.val = key; + imagePrompts.val = imagePromptsMulti.val[key]; + drawSegment(getClicks()); + }, + }, + key, + div( + {}, + button( + { + class: + "btn btn-circle btn-xs btn-ghost group hover:text-red-500", + onclick: (e) => { + console.log("clear"); + e.preventDefault(); + e.stopPropagation(); + imagePrompts.val = []; + imagePromptsMulti.val[key] = []; + drawSegment([]); + updateImagePrompts(); + }, + }, + span({ + class: "iconify", + "data-icon": "ant-design:clear-outlined", + "data-inline": "false", + }) + ), + button( + { + class: + "btn btn-circle btn-xs btn-ghost group hover:text-red-500", + onclick: (e) => { + console.log("delete"); + e.preventDefault(); + e.stopPropagation(); + layer_to_delete.val = key; + setTimeout(() => { + delete_layer_dialog.showModal(); + }, 0); + }, + }, + span({ + class: "iconify", + "data-icon": "ic:baseline-delete", + "data-inline": "false", + }) + ) + ) + ) + ); + }), + div({ class: "divider !py-0 my-0" }), + li( + a( { - type: "button", - class: "btn btn-sm btn-circle btn-ghost absolute right-2 top-2", - onclick: (e) => { - e.stopPropagation(); - my_modal_3.close(); + class: "flex items-center justify-between", + onclick: () => { + my_modal_3.showModal(); }, }, - "✕" - ), - h3( - { class: "font-bold text-lg text-base-content" }, - "Add new layer!" - ), - input({ - type: "text", - placeholder: "Type here", - class: "input input-bordered w-full", - autofocus: true, - }), - button( + "New Layer", + span({ + class: "iconify", + "data-icon": "ic:outline-plus", + "data-inline": "false", + }) + ) + ) + ); + }, + () => + ConfirmDialog( + { + id: "delete_layer_dialog", + title: "Delete Layer: " + layer_to_delete.val, + onsubmit: () => { + imagePromptsMulti.val = Object.fromEntries( + Object.entries(imagePromptsMulti.val).filter( + ([key, value]) => key !== layer_to_delete.val + ) + ); + console.log(imagePromptsMulti.val); + if (selectedLayer.val === layer_to_delete.val) { + // Select another layer if there is one + if (Object.keys(imagePromptsMulti.val).length > 0) { + selectedLayer.val = Object.keys(imagePromptsMulti.val)[0]; + imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; + } else { + selectedLayer.val = ""; + imagePrompts.val = []; + } + } + targetNode.val.graph.change(); + updateImagePrompts(); + delete_layer_dialog.close(); + }, + }, + p("Are you sure you want to delete this layer?") + ), + () => + dialog( + { id: "my_modal_3", class: "modal" }, + div( + { class: "modal-box text-base-content" }, + form( { - type: "submit", - class: "btn btn-sm btn-ghost place-self-end", + class: "gap-2 flex flex-col", + method: "dialog", + onsubmit: (e) => { + console.log("add new layer"); + e.preventDefault(); + const inputText = e.target.elements[1].value; + imagePromptsMulti.val = { + ...imagePromptsMulti.val, + [inputText]: [], + }; + console.log(inputText, imagePromptsMulti.val); + my_modal_3.close(); + e.target.elements[1].value = ""; + + selectedLayer.val = inputText; + imagePrompts.val = imagePromptsMulti.val[inputText]; + drawSegment(getClicks()); + updateImagePrompts(); + }, }, - "Confirm" + button( + { + type: "button", + class: + "btn btn-sm btn-circle btn-ghost absolute right-2 top-2", + onclick: (e) => { + e.stopPropagation(); + my_modal_3.close(); + }, + }, + "✕" + ), + h3( + { class: "font-bold text-lg text-base-content" }, + "Add new layer!" + ), + input({ + type: "text", + placeholder: "Type here", + class: "input input-bordered w-full", + autofocus: true, + }), + button( + { + type: "submit", + class: "btn btn-sm btn-ghost place-self-end", + }, + "Confirm" + ) ) ) ) - ) + ), + // div( + // { + // class: + // "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 right-0 bottom-0 items-start gap-2 bg-transparent", + // }, + // () => { + // return ul( + // { + // class: "menu bg-base-200 w-56 rounded-box text-base-content ", + // }, + // ...allImagePrompts.val.map((e) => + // button( + // { + // class: + // "normal-case text-start flex items-center justify-center", + // onclick: async () => { + // imagePromptsMulti.val = e.prompt; + // imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; + // drawSegment(getClicks()); + // updateImagePrompts() + // }, + // }, + // e.version + // ) + // ) + // ); + // } + // ) ); } diff --git a/js/index.js b/js/index.js index 8cef05b..d217964 100644 --- a/js/index.js +++ b/js/index.js @@ -27,6 +27,7 @@ import "https://code.iconify.design/3/3.1.0/iconify.min.js"; import { autoSegment, drawSegment, getClicks, segmented } from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; import { sharedAvatarLink } from "./AvatarPreview.js"; +import { updateImagePrompts } from './LayerEditor.js'; export const generatedImages = {}; @@ -351,6 +352,7 @@ function showMyImageEditor(node) { embeddings.val = tensor; if (enableAutoSegment.val && !segmented.val) await autoSegment(); drawSegment(getClicks()); + updateImagePrompts() }); targetNode.val = node; }) diff --git a/js/state.js b/js/state.js index a5016d7..88593c2 100644 --- a/js/state.js +++ b/js/state.js @@ -47,6 +47,9 @@ export const imageContainerSize = van.state({ /** @type {State} */ export const imagePrompts = van.state([]); +export const allImagePrompts = van.state([{}]); + + /** @type {State>} */ export const imagePromptsMulti = van.state({}); diff --git a/js/tw-styles.css b/js/tw-styles.css index d343eef..4ee263a 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -2614,6 +2614,10 @@ details.collapse summary::-webkit-details-marker{ background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } +.bg-transparent{ + background-color: transparent; +} + .bg-opacity-50{ --tw-bg-opacity: 0.5; } From b17e2ea383a3946d6bef5bcf36bb63a0920f211b Mon Sep 17 00:00:00 2001 From: EdwinWong Date: Mon, 20 Nov 2023 21:43:52 +0800 Subject: [PATCH 089/127] feat: segment history --- js/LayerEditor.js | 19 +++++++++------ js/SideBar.js | 59 +++++++++++++++++++++++++---------------------- js/tw-styles.css | 30 ++++++++++-------------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 09809b9..5cdb8ee 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -412,13 +412,18 @@ export function LayerEditor() { showImageEditor.val = false; await uploadSegments(); - // allImagePrompts.val = [ - // ...allImagePrompts.val, - // { - // version: "v" + allImagePrompts.val.length, - // prompt: imagePromptsMulti.val, - // }, - // ]; + const isEqual = allImagePrompts.val.map( + (x) => + JSON.stringify(imagePromptsMulti.val) === JSON.stringify(x.prompt) + ); + if (!isEqual.includes(true)) + allImagePrompts.val = [ + ...allImagePrompts.val, + { + version: "v" + allImagePrompts.val.length, + prompt: imagePromptsMulti.val, + }, + ]; // api.fetchApi("/segments_order", { // method: "POST", diff --git a/js/SideBar.js b/js/SideBar.js index 44d21d2..e287d5e 100644 --- a/js/SideBar.js +++ b/js/SideBar.js @@ -257,34 +257,37 @@ export function SideBar() { ) ) ), - // div( - // { - // class: - // "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 right-0 bottom-0 items-start gap-2 bg-transparent", - // }, - // () => { - // return ul( - // { - // class: "menu bg-base-200 w-56 rounded-box text-base-content ", - // }, - // ...allImagePrompts.val.map((e) => - // button( - // { - // class: - // "normal-case text-start flex items-center justify-center", - // onclick: async () => { - // imagePromptsMulti.val = e.prompt; - // imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; - // drawSegment(getClicks()); - // updateImagePrompts() - // }, - // }, - // e.version - // ) - // ) - // ); - // } - // ) + div( + { + class: + "ml-2 z-100 w-fit flex-col flex justify-center absolute top-0 right-0 bottom-0 items-start gap-2 bg-transparent", + }, + () => { + return ul( + { + class: "menu bg-base-200 w-56 rounded-box text-base-content ", + }, + span("Segment History"), + ...allImagePrompts.val.map((e) => + li( + a( + { + class: + "normal-case text-start flex items-center justify-between", + onclick: async () => { + imagePromptsMulti.val = e.prompt; + imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; + drawSegment(getClicks()); + updateImagePrompts(); + }, + }, + e.version + ) + ) + ) + ); + } + ) ); } diff --git a/js/tw-styles.css b/js/tw-styles.css index 4ee263a..e342b35 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com */ /* @@ -501,7 +501,7 @@ html{ :root{ color-scheme: light; --in: 0.7206 0.191 231.6; - --su: 0.7441 0.213 164.75; + --su: 64.8% 0.150 160; --wa: 0.8471 0.199 83.87; --er: 0.7176 0.221 22.18; --pc: 0.89824 0.06192 275.75; @@ -870,8 +870,7 @@ html{ flex-grow: 1; --tw-content: ''; content: var(--tw-content); - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); } .dropdown{ @@ -1152,10 +1151,8 @@ html{ .menu :where(li ul){ position: relative; white-space: nowrap; - -webkit-margin-start: 1rem; - margin-inline-start: 1rem; - -webkit-padding-start: 0.5rem; - padding-inline-start: 0.5rem; + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), @@ -1485,8 +1482,7 @@ details.collapse summary::-webkit-details-marker{ :where(.collapse > input[type="radio"]){ width: 100%; padding: 1rem; - -webkit-padding-end: 3rem; - padding-inline-end: 3rem; + padding-inline-end: 3rem; min-height: 3.75rem; transition: background-color 0.2s ease-out; } @@ -1580,8 +1576,7 @@ details.collapse summary::-webkit-details-marker{ .join > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - -webkit-margin-start: -1px; - margin-inline-start: -1px; + margin-inline-start: -1px; } .link:focus{ @@ -2083,8 +2078,7 @@ details.collapse summary::-webkit-details-marker{ .join.join-horizontal > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - -webkit-margin-start: -1px; - margin-inline-start: -1px; + margin-inline-start: -1px; } .modal-top :where(.modal-box){ @@ -2609,15 +2603,15 @@ details.collapse summary::-webkit-details-marker{ background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } +.bg-transparent{ + background-color: transparent; +} + .bg-white{ --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-transparent{ - background-color: transparent; -} - .bg-opacity-50{ --tw-bg-opacity: 0.5; } From b36bea3694977d83543acb11fc0fdae299b0faeb Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 15:49:17 +0800 Subject: [PATCH 090/127] feat: add bounding box to SAM input --- js/LayerEditor.js | 15 ++++++++++++++- js/onnx.js | 3 ++- js/onnx_helper.js | 31 +++++++++++++++++++++---------- js/state.js | 22 ++++++++++++++++++---- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 5cdb8ee..31bb239 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -16,6 +16,7 @@ import { embeddingID, alertDialog, allImagePrompts, + boxesMulti, } from "./state.js"; import { van } from "./van.js"; import vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3"; @@ -227,6 +228,14 @@ export async function autoSegment() { ...positivePoints, ...negativePoints, ]; + // Find bounding box of positive points + const box = { + x1: Math.min(...negativePoints.map((x) => x.x)), + y1: Math.min(...negativePoints.map((x) => x.y)), + x2: Math.max(...negativePoints.map((x) => x.x)), + y2: Math.max(...negativePoints.map((x) => x.y)), + }; + boxesMulti.val[key] = box; } }); imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; @@ -374,7 +383,11 @@ export async function drawSegment(clicks) { return; } if (embeddings.val) { - const mask = await runONNX(clicks, embeddings.val); + const mask = await runONNX( + clicks, + embeddings.val, + boxesMulti.val[selectedLayer.val] + ); if (mask) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(mask, 0, 0); diff --git a/js/onnx.js b/js/onnx.js index c416cfd..b0ecf57 100644 --- a/js/onnx.js +++ b/js/onnx.js @@ -27,7 +27,7 @@ export const loadNpyTensor = async (tensorFile, dType = "float32") => { return tensor; }; -export const runONNX = async (clicks, tensor) => { +export const runONNX = async (clicks, tensor, box) => { // console.log('tensor', tensor); try { if ( @@ -44,6 +44,7 @@ export const runONNX = async (clicks, tensor) => { clicks, tensor, modelScale: imageSize.val, + box, }); if (feeds === undefined) return; // Run the SAM ONNX model with the feeds returned from modelData() diff --git a/js/onnx_helper.js b/js/onnx_helper.js index 3ab436a..058e3b3 100644 --- a/js/onnx_helper.js +++ b/js/onnx_helper.js @@ -4,7 +4,7 @@ // This source code is licensed under the license found in the // LICENSE file in the root directory of this source tree. -const modelData = ({ clicks, tensor, modelScale }) => { +const modelData = ({ clicks, tensor, modelScale, box }) => { const imageEmbedding = tensor; let pointCoords; let pointLabels; @@ -18,8 +18,9 @@ const modelData = ({ clicks, tensor, modelScale }) => { // If there is no box input, a single padding point with // label -1 and coordinates (0.0, 0.0) should be concatenated // so initialize the array to support (n + 1) points. - pointCoords = new Float32Array(2 * (n + 1)); - pointLabels = new Float32Array(n + 1); + const numPoints = box ? n + 3 : n + 1; + pointCoords = new Float32Array(2 * numPoints); + pointLabels = new Float32Array(numPoints); // Add clicks and scale to what SAM expects for (let i = 0; i < n; i++) { @@ -28,15 +29,25 @@ const modelData = ({ clicks, tensor, modelScale }) => { pointLabels[i] = clicks[i].clickType; } - // Add in the extra point/label when only clicks and no box - // The extra point is at (0, 0) with label -1 - pointCoords[2 * n] = 0.0; - pointCoords[2 * n + 1] = 0.0; - pointLabels[n] = -1.0; + if (box) { + pointCoords[2 * n] = box.x1; + pointCoords[2 * n + 1] = box.y1; + pointLabels[n] = 2; + + pointCoords[2 * n + 2] = box.x2; + pointCoords[2 * n + 3] = box.y2; + pointLabels[n] = 3; + } else { + // Add in the extra point/label when only clicks and no box + // The extra point is at (0, 0) with label -1 + pointCoords[2 * n] = 0.0; + pointCoords[2 * n + 1] = 0.0; + pointLabels[n] = -1.0; + } // Create the tensor - pointCoordsTensor = new ort.Tensor("float32", pointCoords, [1, n + 1, 2]); - pointLabelsTensor = new ort.Tensor("float32", pointLabels, [1, n + 1]); + pointCoordsTensor = new ort.Tensor("float32", pointCoords, [1, numPoints, 2]); + pointLabelsTensor = new ort.Tensor("float32", pointLabels, [1, numPoints]); } const imageSizeTensor = new ort.Tensor("float32", [ modelScale.height, diff --git a/js/state.js b/js/state.js index 88593c2..131edb9 100644 --- a/js/state.js +++ b/js/state.js @@ -10,6 +10,12 @@ * @property {number} x - The x coordinate * @property {number} y - The y coordinate * @property {>} label - The label + * + * @typedef {Object} Box + * @property {number} x1 + * @property {number} y1 + * @property {number} x2 + * @property {number} y2 */ import { van } from "./van.js"; @@ -18,13 +24,15 @@ export const iframeSrc = van.state("https://editor.avatech.ai?comfyui=true"); export const showEditor = van.state(false); // localStorage.getItem("showPreview") == 'true' export const showPreview = van.state(true); -export const previewUrl = van.state("https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true&hideUI=true"); +export const previewUrl = van.state( + "https://editor.avatech.ai/viewer?avatarId=default&debug=false&width=400&height=400&hideTrigger=true&voiceSelection=true&hideUI=true" +); export const previewImg = van.state(""); export const previewImgLoading = van.state(false); export const enableAutoSegment = van.state(false); // export const previewUrl = van.state("http://localhost:3006/viewer?avatarId=default&hideUI=true&debug=true&width=300&height=300&showAudioControl=true"); export const isDirty = van.state(false); -export const fileName = van.state(''); +export const fileName = van.state(""); export const showImageEditor = van.state(false); export const showLoading = van.state(false); export const alertDialog = van.state({ @@ -32,9 +40,9 @@ export const alertDialog = van.state({ time: 0, }); export const shareLoading = van.state(false); -export const previewModelId = van.state(''); +export const previewModelId = van.state(""); -export const isGenerateFlow = van.state(false) +export const isGenerateFlow = van.state(false); export const loadingCaption = van.state(""); export const imageUrl = van.state(""); @@ -44,6 +52,12 @@ export const imageContainerSize = van.state({ height: 0, }); +/** @type {State} */ +export const boxes = van.state(); + +/** @type {State>} */ +export const boxesMulti = van.state({}); + /** @type {State} */ export const imagePrompts = van.state([]); From ea67fc1ceee9ddf0dcdab053593a4965c5a1ca85 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 16:12:06 +0800 Subject: [PATCH 091/127] feat: draw bounding box --- js/LayerEditor.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 31bb239..b8c59cd 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -70,8 +70,8 @@ const layerMapping = { positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, - negativeScale: 0.5, + positiveScale: -0.2, + negativeScale: 1.0, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, }, R_iris: { @@ -80,8 +80,8 @@ const layerMapping = { positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, - negativeScale: 0.5, + positiveScale: -0.2, + negativeScale: 1.0, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, }, face: { @@ -230,10 +230,10 @@ export async function autoSegment() { ]; // Find bounding box of positive points const box = { - x1: Math.min(...negativePoints.map((x) => x.x)), - y1: Math.min(...negativePoints.map((x) => x.y)), - x2: Math.max(...negativePoints.map((x) => x.x)), - y2: Math.max(...negativePoints.map((x) => x.y)), + x1: Math.min(...negativePoints.map((x) => x.x)) - 10, + y1: Math.min(...negativePoints.map((x) => x.y)) - 10, + x2: Math.max(...negativePoints.map((x) => x.x)) + 10, + y2: Math.max(...negativePoints.map((x) => x.y)) + 10, }; boxesMulti.val[key] = box; } @@ -383,14 +383,16 @@ export async function drawSegment(clicks) { return; } if (embeddings.val) { - const mask = await runONNX( - clicks, - embeddings.val, - boxesMulti.val[selectedLayer.val] - ); + const box = boxesMulti.val[selectedLayer.val]; + const mask = await runONNX(clicks, embeddings.val, box); if (mask) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(mask, 0, 0); + if (box) { + ctx.strokeStyle = "green"; + ctx.lineWidth = 5; + ctx.strokeRect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); + } } } } From 5db8a01b764dae2661bc8456aaa4bf322f5de914 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 16:29:36 +0800 Subject: [PATCH 092/127] fix: bbox --- js/onnx_helper.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/onnx_helper.js b/js/onnx_helper.js index 058e3b3..a38aade 100644 --- a/js/onnx_helper.js +++ b/js/onnx_helper.js @@ -30,13 +30,13 @@ const modelData = ({ clicks, tensor, modelScale, box }) => { } if (box) { - pointCoords[2 * n] = box.x1; - pointCoords[2 * n + 1] = box.y1; + pointCoords[2 * n] = box.x1 * modelScale.samScale; + pointCoords[2 * n + 1] = box.y1 * modelScale.samScale; pointLabels[n] = 2; - pointCoords[2 * n + 2] = box.x2; - pointCoords[2 * n + 3] = box.y2; - pointLabels[n] = 3; + pointCoords[2 * n + 2] = box.x2 * modelScale.samScale; + pointCoords[2 * n + 3] = box.y2 * modelScale.samScale; + pointLabels[n + 1] = 3; } else { // Add in the extra point/label when only clicks and no box // The extra point is at (0, 0) with label -1 From 2dfbc84f4f9c4b0602076d80acedf36259485942 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 16:33:48 +0800 Subject: [PATCH 093/127] tweak --- js/LayerEditor.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index b8c59cd..2e638a8 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -71,7 +71,7 @@ const layerMapping = { negativeOffsetX: 0, negativeOffsetY: 0, positiveScale: -0.2, - negativeScale: 1.0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, }, R_iris: { @@ -81,7 +81,7 @@ const layerMapping = { negativeOffsetX: 0, negativeOffsetY: 0, positiveScale: -0.2, - negativeScale: 1.0, + negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, }, face: { @@ -230,10 +230,10 @@ export async function autoSegment() { ]; // Find bounding box of positive points const box = { - x1: Math.min(...negativePoints.map((x) => x.x)) - 10, - y1: Math.min(...negativePoints.map((x) => x.y)) - 10, - x2: Math.max(...negativePoints.map((x) => x.x)) + 10, - y2: Math.max(...negativePoints.map((x) => x.y)) + 10, + x1: Math.min(...negativePoints.map((x) => x.x)), + y1: Math.min(...negativePoints.map((x) => x.y)), + x2: Math.max(...negativePoints.map((x) => x.x)), + y2: Math.max(...negativePoints.map((x) => x.y)), }; boxesMulti.val[key] = box; } From 14ead17dd47621138cc54a7e64980583e051d1ac Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 17:33:05 +0800 Subject: [PATCH 094/127] feat: auto predict breath --- js/LayerEditor.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 2e638a8..b79e61a 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -20,7 +20,7 @@ import { } from "./state.js"; import { van } from "./van.js"; import vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3"; -const { FaceLandmarker, FilesetResolver } = vision; +const { PoseLandmarker, FaceLandmarker, FilesetResolver } = vision; const { button, div, img, canvas, span } = van.tags; let throttle = false; @@ -39,10 +39,18 @@ const faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, { modelAssetPath: `https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task`, delegate: "GPU", }, - outputFaceBlendshapes: true, + // outputFaceBlendshapes: true, runningMode: "IMAGE", numFaces: 1, }); +const poseLandmarker = await PoseLandmarker.createFromOptions(filesetResolver, { + baseOptions: { + modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_full/float16/1/pose_landmarker_full.task`, + delegate: "GPU", + }, + runningMode: "IMAGE", + numPoses: 1, +}); const layerMapping = { L_eye: { useMiddle: false, @@ -121,6 +129,7 @@ export const segmented = van.state(false); export async function autoSegment() { const image = document.getElementById("image"); const landmarks = faceLandmarker.detect(image).faceLandmarks[0]; + Object.entries(layerMapping).forEach(([key, value]) => { imagePromptsMulti.val[key] = []; }); @@ -238,6 +247,12 @@ export async function autoSegment() { boxesMulti.val[key] = box; } }); + const poseLandmarks = poseLandmarker.detect(image).landmarks[0]; + const breathX = + ((poseLandmarks[11].x + poseLandmarks[12].x) / 2) * imageSize.val.width; + const breathY = + ((poseLandmarks[11].y + poseLandmarks[12].y) / 2) * imageSize.val.height; + imagePromptsMulti.val["breath"] = [{ x: breathX, y: breathY, label: 1 }]; imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; segmented.val = true; console.log("Done"); From 6d74a00019fe38a990508416b4335c05278b81a7 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 21 Nov 2023 18:01:35 +0800 Subject: [PATCH 095/127] fix: missing bbox --- js/LayerEditor.js | 59 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index b79e61a..4a8b676 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -109,7 +109,7 @@ const layerMapping = { negativeOffsetX: 0, negativeOffsetY: 0, positiveScale: 0, - negativeScale: 0.5, + negativeScale: 0, indices: FaceLandmarker.FACE_LANDMARKS_LIPS, }, mouth_in: { @@ -119,7 +119,7 @@ const layerMapping = { negativeOffsetX: 0, negativeOffsetY: 0, positiveScale: 0, - negativeScale: 0.5, + negativeScale: 0, indices: FaceLandmarker.FACE_LANDMARKS_LIPS, }, }; @@ -198,21 +198,25 @@ export async function autoSegment() { directionVector.x * directionVector.x + directionVector.y * directionVector.y ); - const negativePointDistance = - value.negativeScale * directionVectorLength; - const negativePoint = { - x: - startX - - (negativePointDistance * directionVector.x) / - directionVectorLength - - value.negativeOffsetX, - y: - startY - - (negativePointDistance * directionVector.y) / - directionVectorLength - - value.negativeOffsetY, - label: 0, - }; + + if (value.negativeScale !== 0) { + const negativePointDistance = + value.negativeScale * directionVectorLength; + const negativePoint = { + x: + startX - + (negativePointDistance * directionVector.x) / + directionVectorLength - + value.negativeOffsetX, + y: + startY - + (negativePointDistance * directionVector.y) / + directionVectorLength - + value.negativeOffsetY, + label: 0, + }; + negativePoints.push(negativePoint); + } const positivePointDistance = value.positiveScale * directionVectorLength; @@ -229,24 +233,25 @@ export async function autoSegment() { value.positiveOffsetY, label: 1, }; - - negativePoints.push(negativePoint); } imagePromptsMulti.val[key] = [ ...imagePromptsMulti.val[key], ...positivePoints, ...negativePoints, ]; - // Find bounding box of positive points - const box = { - x1: Math.min(...negativePoints.map((x) => x.x)), - y1: Math.min(...negativePoints.map((x) => x.y)), - x2: Math.max(...negativePoints.map((x) => x.x)), - y2: Math.max(...negativePoints.map((x) => x.y)), - }; - boxesMulti.val[key] = box; } + + // Find bounding box of positive/negative points + const points = negativePoints.length > 0 ? negativePoints : positivePoints; + const box = { + x1: Math.min(...points.map((x) => x.x)), + y1: Math.min(...points.map((x) => x.y)), + x2: Math.max(...points.map((x) => x.x)), + y2: Math.max(...points.map((x) => x.y)), + }; + boxesMulti.val[key] = box; }); + const poseLandmarks = poseLandmarker.detect(image).landmarks[0]; const breathX = ((poseLandmarks[11].x + poseLandmarks[12].x) / 2) * imageSize.val.width; From 5ff4ec9884c52eec33dfd6e8c125d6f6fc13ee4d Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 22 Nov 2023 18:08:10 +0800 Subject: [PATCH 096/127] feat: remote SAM embedding --- js/index.js | 12 ++++-- routes.py | 103 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/js/index.js b/js/index.js index d217964..1d7ef47 100644 --- a/js/index.js +++ b/js/index.js @@ -24,10 +24,15 @@ import { api } from "./api.js"; import { Container } from "./Container.js"; import { initModel, loadNpyTensor } from "./onnx.js"; import "https://code.iconify.design/3/3.1.0/iconify.min.js"; -import { autoSegment, drawSegment, getClicks, segmented } from "./LayerEditor.js"; +import { + autoSegment, + drawSegment, + getClicks, + segmented, +} from "./LayerEditor.js"; import { infoDialog } from "./dialog.js"; import { sharedAvatarLink } from "./AvatarPreview.js"; -import { updateImagePrompts } from './LayerEditor.js'; +import { updateImagePrompts } from "./LayerEditor.js"; export const generatedImages = {}; @@ -316,6 +321,7 @@ function showMyImageEditor(node) { isGeneratedImage, embedding_id: id, ckpt, + remote: true, }), }) .then(() => { @@ -352,7 +358,7 @@ function showMyImageEditor(node) { embeddings.val = tensor; if (enableAutoSegment.val && !segmented.val) await autoSegment(); drawSegment(getClicks()); - updateImagePrompts() + updateImagePrompts(); }); targetNode.val = node; }) diff --git a/routes.py b/routes.py index 8a6bd17..038f731 100644 --- a/routes.py +++ b/routes.py @@ -10,9 +10,12 @@ import numpy as np import server import re import base64 +from PIL import Image +import io load_dotenv() + # For speeding up ONNX model, see https://github.com/facebookresearch/segment-anything/tree/main/demo#onnx-multithreading-with-sharedarraybuffer def inject_headers(original_handler): async def _handler(request): @@ -36,11 +39,13 @@ for item in server.PromptServer.instance.routes._items: routes.append(item) server.PromptServer.instance.routes._items = routes + @server.PromptServer.instance.routes.get("/avatar-graph-comfyui/tw-styles.css") async def get_web_styles(request): filename = os.path.join(os.path.dirname(__file__), "js/tw-styles.css") return web.FileResponse(filename) + @server.PromptServer.instance.routes.get("/sam_model") async def get_sam_model(request): model_type = request.rel_url.query.get("type", "vit_h") @@ -77,26 +82,56 @@ async def post_sam_model(request): emb_id = post.get("embedding_id") ckpt = post.get("ckpt") ckpt = folder_paths.get_full_path("sams", ckpt) - model_type = re.findall(r'vit_[lbh]', ckpt)[0] + remote = post.get("remote") + model_type = re.findall(r"vit_[lbh]", ckpt)[0] emb_filename = f"{folder_paths.get_output_directory()}/{emb_id}_{model_type}.npy" + output_json_filename = ( + f"{folder_paths.get_output_directory()}/{emb_id}_{model_type}.json" + ) if not os.path.exists(emb_filename): image = load_image(post.get("image"), is_generated_image) - - sam = sam_model_registry[model_type](checkpoint=ckpt) - predictor = SamPredictor(sam) - - image_np = (image * 255).astype(np.uint8) - predictor.set_image(image_np) - emb = predictor.get_image_embedding().cpu().numpy() - np.save(emb_filename, emb) - with open(f"{folder_paths.get_output_directory()}/{emb_id}_{model_type}.json", "w") as f: - json.dump( - { - "input_size": predictor.input_size, - "original_size": predictor.original_size, + if remote: + # Run embed in remote server + image = Image.fromarray((image * 255).astype(np.uint8)) + buffered = io.BytesIO() + image.save(buffered, format="PNG") + image = base64.b64encode(buffered.getvalue()).decode() + res = requests.post( + "https://avatechgg--sam-embed.modal.run", + headers={ + "Content-type": "application/json", + "Accept": "application/json", }, - f, - ) + data=json.dumps({ + "image": image, + }), + ).json() + emb, input_size, original_size = res["emb"], res["input_size"], res["original_size"] + emb = np.array(emb).astype(np.float32) + np.save(emb_filename, emb) + with open(output_json_filename, "w") as f: + data = { + "input_size": input_size, + "original_size": original_size, + } + json.dump(data, f) + else: + sam = sam_model_registry[model_type](checkpoint=ckpt) + predictor = SamPredictor(sam) + + image_np = (image * 255).astype(np.uint8) + predictor.set_image(image_np) + emb = predictor.get_image_embedding().cpu().numpy() + np.save(emb_filename, emb) + with open(output_json_filename, "w") as f: + json.dump( + { + "input_size": predictor.input_size, + "original_size": predictor.original_size, + }, + f, + ) + print("Finished embedding") return web.json_response({}) @@ -110,6 +145,7 @@ async def post_sam_model(request): # response.raise_for_status() # return web.json_response(response.json()) + @server.PromptServer.instance.routes.get("/get_workflow") async def get_workflow(request): name = request.rel_url.query.get("name", "default") @@ -121,7 +157,7 @@ async def get_workflow(request): # else: if name == "default": name = "Auto_segment_workflow" - + workflows_path = os.path.join(os.path.dirname(__file__), "workflow_templates") workflow = json.load(open(f"{workflows_path}/{name}.json")) return web.json_response(workflow) @@ -140,7 +176,7 @@ async def post_segments(request): f.write(base64.b64decode(value.split(",")[1])) order = list(segments.keys()) - with open(os.path.join(output_dir, "order.json") , "w") as f: + with open(os.path.join(output_dir, "order.json"), "w") as f: json.dump(order, f) return web.json_response({}) @@ -156,13 +192,16 @@ async def post_segments(request): # json.dump(order, f) # return web.json_response({}) + @server.PromptServer.instance.routes.get("/get_webhook") async def get_webhook(request): - url = os.getenv('DISCORD_WEBHOOK_URL') + url = os.getenv("DISCORD_WEBHOOK_URL") return web.json_response(url) + import uuid + @server.PromptServer.instance.routes.post("/create_avatar_from_image") async def post_input_file(request): post = await request.read() @@ -173,20 +212,22 @@ async def post_input_file(request): try: queue_id = uuid.uuid4() - - output_dir = os.path.join(folder_paths.base_path, "input","create_avatar_endpoint") + + output_dir = os.path.join( + folder_paths.base_path, "input", "create_avatar_endpoint" + ) os.makedirs(output_dir, exist_ok=True) - - filename = os.path.join(output_dir,str(queue_id) + ".png") + + filename = os.path.join(output_dir, str(queue_id) + ".png") with open(filename, "wb") as f: f.write(post) - - return web.json_response({ - "redirect_url": "https://ai-assistant.avatech.ai?queue-id=" + str(queue_id) - }) + + return web.json_response( + { + "redirect_url": "https://ai-assistant.avatech.ai?queue-id=" + + str(queue_id) + } + ) except Exception as e: print(e) - return web.json_response({ - "error": e - }) - + return web.json_response({"error": e}) From 005b0e533ed5ab2e77d12aa0c85902412ddc624b Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Wed, 22 Nov 2023 07:20:51 -0500 Subject: [PATCH 097/127] fix: seems fix StructRNA --- blender/blender_node.py | 4 ++-- blender/ops_join.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/blender/blender_node.py b/blender/blender_node.py index cc3ed96..79cf0ca 100644 --- a/blender/blender_node.py +++ b/blender/blender_node.py @@ -100,14 +100,14 @@ class ObjectOps: import global_bpy bpy = global_bpy.get_bpy() - if props.get("BPY_OBJ") != None: + if props.get("BPY_OBJ") is not None: bpy.context.view_layer.objects.active = props["BPY_OBJ"] results = self.blender_process(bpy, **props) if results is None: # print(results) - if props.get("BPY_OBJ") != None: + if props.get("BPY_OBJ") is not None: return (props["BPY_OBJ"], ) else: return (bpy.context.view_layer.objects.active, ) diff --git a/blender/ops_join.py b/blender/ops_join.py index 557e805..368761a 100644 --- a/blender/ops_join.py +++ b/blender/ops_join.py @@ -11,8 +11,10 @@ class Mesh_JoinMesh(blender_node.ObjectOps): def blender_process(self, bpy, BPY_OBJ, **props): prop_values = props.values() for obj in list(prop_values) + [BPY_OBJ]: - obj.select_set(True) - bpy.context.view_layer.objects.active = BPY_OBJ + if obj is not None: + obj.select_set(True) + if bpy.context.view_layer.objects is not None: + bpy.context.view_layer.objects.active = BPY_OBJ bpy.ops.object.join() return (BPY_OBJ,) From 5d15f76f46119a7feaf8c04e69429dad6ee2bcc8 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 24 Nov 2023 13:10:16 +0800 Subject: [PATCH 098/127] fix: autoseg --- js/LayerEditor.js | 13 +++++++++---- js/index.js | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 4a8b676..6080fa6 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -325,10 +325,15 @@ export async function uploadSegments() { const segments = {}; for (const [layer, prompts] of Object.entries(imagePromptsMulti.val)) { - await drawSegment(getClicks(prompts)); + await drawSegment(getClicks(prompts), layer, false); const canvas = document.getElementById("mask-canvas"); const base64Image = canvas.toDataURL(); segments[layer] = base64Image; + // download image + // const a = document.createElement("a"); + // a.href = base64Image; + // a.download = layer + ".png"; + // a.click(); } await api.fetchApi("/segments", { method: "POST", @@ -395,7 +400,7 @@ export function getClicks(prompts) { })); } -export async function drawSegment(clicks) { +export async function drawSegment(clicks, layer, withBox = true) { const canvas = document.getElementById("mask-canvas"); const ctx = canvas.getContext("2d"); if (clicks.length === 0) { @@ -403,12 +408,12 @@ export async function drawSegment(clicks) { return; } if (embeddings.val) { - const box = boxesMulti.val[selectedLayer.val]; + const box = boxesMulti.val[layer || selectedLayer.val]; const mask = await runONNX(clicks, embeddings.val, box); if (mask) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(mask, 0, 0); - if (box) { + if (box && withBox) { ctx.strokeStyle = "green"; ctx.lineWidth = 5; ctx.strokeRect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); diff --git a/js/index.js b/js/index.js index 1d7ef47..ce6e85c 100644 --- a/js/index.js +++ b/js/index.js @@ -321,7 +321,7 @@ function showMyImageEditor(node) { isGeneratedImage, embedding_id: id, ckpt, - remote: true, + // remote: true, }), }) .then(() => { From 7b6ae6c5b2fa5652c10cb6c011e0e956f7c3bc5f Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 24 Nov 2023 17:24:06 +0800 Subject: [PATCH 099/127] feat: auto segment face in server --- requirements.txt | 1 + sam/sam_multilayer.py | 273 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 240 insertions(+), 34 deletions(-) diff --git a/requirements.txt b/requirements.txt index 98e4f81..7a88a92 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ bpy segment-anything tqdm python-dotenv +mediapipe # -e git+https://github.com/facebookresearch/segment-anything.git#egg=segment_anything \ No newline at end of file diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index eecbe92..b5e7533 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -7,9 +7,91 @@ import json from segment_anything import sam_model_registry, SamPredictor from einops import rearrange, repeat from PIL import Image +import mediapipe as mp +from math import sqrt +BaseOptions = mp.tasks.BaseOptions +FaceLandmarker = mp.tasks.vision.FaceLandmarker +FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions +VisionRunningMode = mp.tasks.vision.RunningMode global_predictor = None +face_landmarker = None + +# For auto-segmentation +layerMapping = { + "L_eye": { + "useMiddle": False, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": 0, + "negativeScale": 0.5, + "indices": mp.solutions.face_mesh.FACEMESH_LEFT_EYE, + }, + "R_eye": { + "useMiddle": False, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": 0, + "negativeScale": 0.5, + "indices": mp.solutions.face_mesh.FACEMESH_RIGHT_EYE, + }, + "L_iris": { + "useMiddle": False, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": -0.2, + "negativeScale": 0.5, + "indices": mp.solutions.face_mesh.FACEMESH_LEFT_IRIS, + }, + "R_iris": { + "useMiddle": False, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": -0.2, + "negativeScale": 0.5, + "indices": mp.solutions.face_mesh.FACEMESH_RIGHT_IRIS, + }, + "face": { + "useMiddle": False, + "positiveOffsetX": 0, + "positiveOffsetY": 40, + "negativeOffsetX": 0, + "negativeOffsetY": 60, + "positiveScale": 0.2, + "negativeScale": 0.6, + "indices": mp.solutions.face_mesh.FACEMESH_FACE_OVAL, + }, + "mouth": { + "useMiddle": True, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": 0, + "negativeScale": 0, + "indices": mp.solutions.face_mesh.FACEMESH_LIPS, + }, + "mouth_in": { + "useMiddle": True, + "positiveOffsetX": 0, + "positiveOffsetY": 0, + "negativeOffsetX": 0, + "negativeOffsetY": 0, + "positiveScale": 0, + "negativeScale": 0, + "indices": mp.solutions.face_mesh.FACEMESH_LIPS, + }, +} + class SAMMultiLayer: def __init__(self): @@ -18,12 +100,6 @@ class SAMMultiLayer: @classmethod def INPUT_TYPES(s): - input_dir = folder_paths.get_input_directory() - files = [ - f - for f in os.listdir(input_dir) - if os.path.isfile(os.path.join(input_dir, f)) - ] return { "required": { "image": ("IMAGE",), @@ -32,7 +108,6 @@ class SAMMultiLayer: "STRING", {"multiline": False, "default": "embedding"}, ), - # "image": (sorted(files), ), "image_prompts_json": ("STRING", {"multiline": False, "default": "[]"}), }, } @@ -42,9 +117,129 @@ class SAMMultiLayer: RETURN_TYPES = ("SAM_PROMPT",) FUNCTION = "load_image" + def load_models(self, ckpt, model_type): + global global_predictor, face_landmarker + + ckpt = folder_paths.get_full_path("sams", ckpt) + sam = sam_model_registry[model_type](checkpoint=ckpt) # .to("cuda") + global_predictor = SamPredictor(sam) + + model_path = "/home/avatech/Desktop/projects/ComfyUI/custom_nodes/avatar-graph-comfyui/sam/face_landmarker.task" + options = FaceLandmarkerOptions( + base_options=BaseOptions(model_asset_path=model_path), + running_mode=VisionRunningMode.IMAGE, + ) + face_landmarker = FaceLandmarker.create_from_options(options) + + return global_predictor, face_landmarker + + def auto_segment(self, image, face_landmarks): + H, W, C = image.shape + imagePromptsMulti = {} + boxesMulti = {} + + for key, value in layerMapping.items(): + positivePoints = [] + middlePoints = [] + negativePoints = [] + + for index in value["indices"]: + start, end = index + startPoint = face_landmarks[start] + + startX = startPoint.x * W + startY = startPoint.y * H + + if len(middlePoints) == 0: + middlePoints.append({"x": startX, "y": startY, "label": 1}) + else: + middlePoints[0]["x"] += startX + middlePoints[0]["y"] += startY + + positivePoints.append({"x": startX, "y": startY, "label": 1}) + + len_indices = len(value["indices"]) + middlePoints[0]["x"] /= len_indices + middlePoints[0]["y"] /= len_indices + + if value["useMiddle"]: + imagePromptsMulti[key] = middlePoints + else: + for i, index in enumerate(value["indices"]): + start, end = index + startPoint = face_landmarks[start] + + startX = startPoint.x * W + startY = startPoint.y * H + + middlePoint = middlePoints[0] + directionVector = { + "x": middlePoint["x"] - startX, + "y": middlePoint["y"] - startY, + } + directionVectorLength = sqrt( + directionVector["x"] * directionVector["x"] + + directionVector["y"] * directionVector["y"] + ) + + if value["negativeScale"] != 0: + negativePointDistance = ( + value["negativeScale"] * directionVectorLength + ) + negativePoint = { + "x": startX + - (negativePointDistance * directionVector["x"]) + / directionVectorLength + - value["negativeOffsetX"], + "y": startY + - (negativePointDistance * directionVector["y"]) + / directionVectorLength + - value["negativeOffsetY"], + "label": 0, + } + negativePoints.append(negativePoint) + + positivePointDistance = ( + value["positiveScale"] * directionVectorLength + ) + positivePoints[i] = { + "x": positivePoints[i]["x"] + - (positivePointDistance * directionVector["x"]) + / directionVectorLength + - value["positiveOffsetX"], + "y": positivePoints[i]["y"] + - (positivePointDistance * directionVector["y"]) + / directionVectorLength + - value["positiveOffsetY"], + "label": 1, + } + + imagePromptsMulti[key] = positivePoints + negativePoints + + points = negativePoints if len(negativePoints) > 0 else positivePoints + box = np.array([ + min(x["x"] for x in points), + min(x["y"] for x in points), + max(x["x"] for x in points), + max(x["y"] for x in points), + ]) + boxesMulti[key] = box + + return imagePromptsMulti, boxesMulti + + def detect_face(self, np_image): + global face_landmarker + mp_image = mp.Image( + image_format=mp.ImageFormat.SRGB, data=(np_image * 255).astype(np.uint8) + ) + face_landmarks = face_landmarker.detect(mp_image).face_landmarks[0] + imagePromptsMulti, boxesMulti = self.auto_segment(np_image, face_landmarks) + + return imagePromptsMulti, boxesMulti + def load_image(self, image, ckpt, embedding_id, image_prompts_json): image_prompts = json.loads(image_prompts_json) - + order_file = f"{self.output_dir}/segments_{embedding_id}/order.json" if os.path.exists(order_file): # Frontend uploads segments images to backend => backend reads all segments images and passes them to next nodes @@ -54,7 +249,9 @@ class SAMMultiLayer: result = [image_prompts] for segment in order: - image = Image.open(f"{self.output_dir}/segments_{embedding_id}/{segment}.png") + image = Image.open( + f"{self.output_dir}/segments_{embedding_id}/{segment}.png" + ) image = np.array(image).astype(np.float32) / 255.0 image = torch.from_numpy(image)[None,] result.append(image) @@ -62,16 +259,12 @@ class SAMMultiLayer: return result else: # Frontend uploads clicks coordinates to backend => backend runs SAM and passes the segments to next nodes - global global_predictor - model_type = re.findall(r'vit_[lbh]', ckpt)[0] + model_type = re.findall(r"vit_[lbh]", ckpt)[0] + global global_predictor, face_landmarker if global_predictor is None: - ckpt = folder_paths.get_full_path("sams", ckpt) - sam = sam_model_registry[model_type](checkpoint=ckpt) - predictor = SamPredictor(sam) - global_predictor = predictor - - predictor = global_predictor + global_predictor, face_landmarker = self.load_models(ckpt, model_type) + print(face_landmarker) if image.shape[3] == 4: image = image[:, :, :, :3] @@ -79,14 +272,16 @@ class SAMMultiLayer: emb_filename = f"{self.output_dir}/{embedding_id}_{model_type}.npy" if not os.path.exists(emb_filename): image_np = (image[0].numpy() * 255).astype(np.uint8) - predictor.set_image(image_np) - emb = predictor.get_image_embedding().cpu().numpy() + global_predictor.set_image(image_np) + emb = global_predictor.get_image_embedding().cpu().numpy() np.save(emb_filename, emb) - with open(f"{self.output_dir}/{embedding_id}_{model_type}.json", "w") as f: + with open( + f"{self.output_dir}/{embedding_id}_{model_type}.json", "w" + ) as f: data = { - "input_size": predictor.input_size, - "original_size": predictor.original_size, + "input_size": global_predictor.input_size, + "original_size": global_predictor.original_size, } json.dump(data, f) else: @@ -94,33 +289,43 @@ class SAMMultiLayer: with open(f"{self.output_dir}/{embedding_id}_{model_type}.json") as f: data = json.load(f) - predictor.input_size = data["input_size"] - predictor.features = torch.from_numpy(emb) - predictor.is_image_set = True - predictor.original_size = data["original_size"] + global_predictor.input_size = data["input_size"] + global_predictor.features = torch.from_numpy(emb) + global_predictor.is_image_set = True + global_predictor.original_size = data["original_size"] + + imagePromptsMulti, boxesMulti = self.detect_face( + image[0].numpy().astype(np.float32) + ) image_prompts = json.loads(image_prompts_json) - result = [image_prompts] if isinstance(image_prompts, list): pass elif all(isinstance(item, list) for item in image_prompts.values()): - for item in image_prompts.values(): - if (len(item) == 0): + for key, item in image_prompts.items(): + if len(item) == 0: h, w, c = image[0].shape result.append(torch.zeros(1, h, w, c)) continue - point_coords = np.array([[p['x'], p['y']] for p in item]) - point_labels = np.array([p['label'] for p in item]) - masks, _, _ = predictor.predict( + points = ( + item + imagePromptsMulti[key] + if key in imagePromptsMulti + else item + ) + point_coords = np.array([[p["x"], p["y"]] for p in points]) + point_labels = np.array([p["label"] for p in points]) + + masks, _, _ = global_predictor.predict( point_coords=point_coords, point_labels=point_labels, + box=boxesMulti[key] if key in boxesMulti else None, ) masks = torch.from_numpy(masks) - masks = rearrange(masks[0], 'h w -> 1 h w') - out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image + masks = rearrange(masks[0], "h w -> 1 h w") + out_image = repeat(masks, "1 h w -> 1 h w c", c=3) * image result.append(out_image) return result From 94e35224c4d36c729b6a311b05d5cc1261680685 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 24 Nov 2023 17:47:09 +0800 Subject: [PATCH 100/127] feat: download mediapipe model --- __init__.py | 54 +++++++++++++++++++++++++++++++------------ sam/sam_multilayer.py | 20 ++++++++-------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/__init__.py b/__init__.py index 1fcb5a2..5359d3d 100644 --- a/__init__.py +++ b/__init__.py @@ -43,7 +43,29 @@ def append_to_sys_path(path): if path not in sys.path: sys.path.append(path) -folder_paths.folder_names_and_paths["sams"] = ([os.path.join(folder_paths.models_dir, "sams")], folder_paths.supported_pt_extensions) + +folder_paths.folder_names_and_paths["sams"] = ( + [os.path.join(folder_paths.models_dir, "sams")], + folder_paths.supported_pt_extensions, +) + + +def download_model(url, save_path): + response = requests.get(url, stream=True) + response.raise_for_status() + file_size = int(response.headers.get("Content-Length", 0)) + chunk_size = 1024 + num_bars = int(file_size / chunk_size) + + with open(save_path, "wb") as f: + for chunk in tqdm( + response.iter_content(chunk_size=chunk_size), + total=num_bars, + unit="KB", + desc=url.split("/")[-1], + ): + f.write(chunk) + def download_sam_model(): model_dir = get_folder_paths("sams")[0] @@ -56,24 +78,26 @@ def download_sam_model(): if "sam_vit_h_4b8939.pth" not in files: print("Downloading sam model...") url = "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth" - response = requests.get(url, stream=True) - response.raise_for_status() - file_size = int(response.headers.get("Content-Length", 0)) - chunk_size = 1024 - num_bars = int(file_size / chunk_size) - - with open(f"{model_dir}/sam_vit_h_4b8939.pth", "wb") as f: - for chunk in tqdm( - response.iter_content(chunk_size=chunk_size), - total=num_bars, - unit="KB", - desc=url.split("/")[-1], - ): - f.write(chunk) + download_model(url, f"{model_dir}/sam_vit_h_4b8939.pth") download_sam_model() + +def download_face_landmarker(): + model_dir = os.path.join(ag_path, "mediapipe_models") + if not os.path.isdir(model_dir): + os.makedirs(model_dir) + + model_path = os.path.join(model_dir, "face_landmarker.task") + if not os.path.isfile(model_path): + print("Downloading mediapipe model...") + url = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task" + download_model(url, model_path) + + +download_face_landmarker() + paths = ["blender", "sam"] files = [] diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index b5e7533..ce036ab 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -124,7 +124,7 @@ class SAMMultiLayer: sam = sam_model_registry[model_type](checkpoint=ckpt) # .to("cuda") global_predictor = SamPredictor(sam) - model_path = "/home/avatech/Desktop/projects/ComfyUI/custom_nodes/avatar-graph-comfyui/sam/face_landmarker.task" + model_path = os.path.join(os.path.dirname(__file__), "../mediapipe_models/face_landmarker.task") options = FaceLandmarkerOptions( base_options=BaseOptions(model_asset_path=model_path), running_mode=VisionRunningMode.IMAGE, @@ -217,12 +217,14 @@ class SAMMultiLayer: imagePromptsMulti[key] = positivePoints + negativePoints points = negativePoints if len(negativePoints) > 0 else positivePoints - box = np.array([ - min(x["x"] for x in points), - min(x["y"] for x in points), - max(x["x"] for x in points), - max(x["y"] for x in points), - ]) + box = np.array( + [ + min(x["x"] for x in points), + min(x["y"] for x in points), + max(x["x"] for x in points), + max(x["y"] for x in points), + ] + ) boxesMulti[key] = box return imagePromptsMulti, boxesMulti @@ -294,9 +296,7 @@ class SAMMultiLayer: global_predictor.is_image_set = True global_predictor.original_size = data["original_size"] - imagePromptsMulti, boxesMulti = self.detect_face( - image[0].numpy().astype(np.float32) - ) + imagePromptsMulti, boxesMulti = self.detect_face(image[0].numpy()) image_prompts = json.loads(image_prompts_json) result = [image_prompts] From 34c5dcf65be0fd497cacc1d7d26351b695eb9f21 Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 24 Nov 2023 18:47:07 +0800 Subject: [PATCH 101/127] feat: button for toggling auto segment --- js/LayerEditor.js | 176 +++++++++++++++++++++++++++------------------- js/tw-styles.css | 34 ++++----- 2 files changed, 122 insertions(+), 88 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 6080fa6..7845a04 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -17,6 +17,7 @@ import { alertDialog, allImagePrompts, boxesMulti, + enableAutoSegment, } from "./state.js"; import { van } from "./van.js"; import vision from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3"; @@ -151,7 +152,7 @@ export async function autoSegment() { // const endY = endPoint.y * imageSize.val.height; if (middlePoints.length === 0) { - middlePoints.push({ x: startX, y: startY, label: 1 }); + middlePoints.push({ x: startX, y: startY, label: 1, isAuto: true }); // middlePoints.push({ x: endX, y: endY, label: 1 }); } else { middlePoints[0].x += startX; @@ -159,7 +160,7 @@ export async function autoSegment() { // middlePoints[1].x += endX; // middlePoints[1].y += endY; } - positivePoints.push({ x: startX, y: startY, label: 1 }); + positivePoints.push({ x: startX, y: startY, label: 1, isAuto: true }); // positivePoints.push({ x: endX, y: endY, label: 1 }); // imagePrompts.val = [...imagePrompts.val, { x, y, label: 1 }]; @@ -214,6 +215,7 @@ export async function autoSegment() { directionVectorLength - value.negativeOffsetY, label: 0, + isAuto: true, }; negativePoints.push(negativePoint); } @@ -232,6 +234,7 @@ export async function autoSegment() { directionVectorLength - value.positiveOffsetY, label: 1, + isAuto: true, }; } imagePromptsMulti.val[key] = [ @@ -257,7 +260,9 @@ export async function autoSegment() { ((poseLandmarks[11].x + poseLandmarks[12].x) / 2) * imageSize.val.width; const breathY = ((poseLandmarks[11].y + poseLandmarks[12].y) / 2) * imageSize.val.height; - imagePromptsMulti.val["breath"] = [{ x: breathX, y: breathY, label: 1 }]; + imagePromptsMulti.val["breath"] = [ + { x: breathX, y: breathY, label: 1, isAuto: true }, + ]; imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; segmented.val = true; console.log("Done"); @@ -397,10 +402,11 @@ export function getClicks(prompts) { x: point.x, y: point.y, clickType: point.label, + isAuto: point.isAuto, })); } -export async function drawSegment(clicks, layer, withBox = true) { +export async function drawSegment(clicks, layer, drawBox = true) { const canvas = document.getElementById("mask-canvas"); const ctx = canvas.getContext("2d"); if (clicks.length === 0) { @@ -408,12 +414,21 @@ export async function drawSegment(clicks, layer, withBox = true) { return; } if (embeddings.val) { - const box = boxesMulti.val[layer || selectedLayer.val]; - const mask = await runONNX(clicks, embeddings.val, box); + const box = enableAutoSegment.val + ? boxesMulti.val[layer || selectedLayer.val] + : null; + const filteredClicks = enableAutoSegment.val + ? clicks + : clicks.filter((click) => !click.isAuto); + if (filteredClicks.length === 0) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + return; + } + const mask = await runONNX(filteredClicks, embeddings.val, box); if (mask) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(mask, 0, 0); - if (box && withBox) { + if (box && drawBox) { ctx.strokeStyle = "green"; ctx.lineWidth = 5; ctx.strokeRect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); @@ -443,75 +458,89 @@ export function LayerEditor() { "absolute flex bg-gray-900 bg-opacity-50 top-0 w-full h-full pointer-events-auto z-[1000] " + (showImageEditor.val ? "" : "hidden"), }, - button( + div( { - class: () => - "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-2 top-0 z-[200] w-fit", - onclick: async () => { - console.log("close"); - showImageEditor.val = false; - await uploadSegments(); + class: + "absolute top-4 left-4 right-0 flex w-full gap-2 justify-start z-[200]", + }, + button( + { + class: () => "btn btn-neutral flex flex-row normal-case rounded-md", + onclick: async () => { + console.log("close"); + showImageEditor.val = false; + await uploadSegments(); - const isEqual = allImagePrompts.val.map( - (x) => - JSON.stringify(imagePromptsMulti.val) === JSON.stringify(x.prompt) - ); - if (!isEqual.includes(true)) - allImagePrompts.val = [ - ...allImagePrompts.val, - { - version: "v" + allImagePrompts.val.length, - prompt: imagePromptsMulti.val, - }, - ]; + const isEqual = allImagePrompts.val.map( + (x) => + JSON.stringify(imagePromptsMulti.val) === + JSON.stringify(x.prompt) + ); + if (!isEqual.includes(true)) + allImagePrompts.val = [ + ...allImagePrompts.val, + { + version: "v" + allImagePrompts.val.length, + prompt: imagePromptsMulti.val, + }, + ]; - // api.fetchApi("/segments_order", { - // method: "POST", - // body: JSON.stringify({ - // name: embeddingID.val, - // order: Object.keys(imagePromptsMulti.val), - // }), - // }); + // api.fetchApi("/segments_order", { + // method: "POST", + // body: JSON.stringify({ + // name: embeddingID.val, + // order: Object.keys(imagePromptsMulti.val), + // }), + // }); + }, }, - }, - span({ - class: "iconify text-lg", - "data-icon": "ic:baseline-arrow-back", - "data-inline": "false", - }), - div("Back") - ), - button( - { - class: () => - "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-28 top-0 z-[200] w-fit", - onclick: () => (showSidebar.val = !showSidebar.val), - }, - div(() => (showSidebar.val ? "Hide UI" : "Show UI")) - ), - button( - { - class: () => - "btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit", - onclick: () => { - enableBackgroundRemover.val = !enableBackgroundRemover.val; - setRemoveBackgroundNode(); + span({ + class: "iconify text-lg", + "data-icon": "ic:baseline-arrow-back", + "data-inline": "false", + }), + div("Back") + ), + button( + { + class: () => "btn btn-neutral flex flex-row normal-case rounded-md", + onclick: () => (showSidebar.val = !showSidebar.val), }, - }, - () => - enableBackgroundRemover.val - ? "Background Remover On" - : "Background Remover Off" - ), - button( - { - class: () => - `btn btn-neutral flex flex-row normal-case absolute mt-4 rounded-md left-52 top-0 z-[200] w-fit ${ - isMobileDevice() ? "" : "hidden" - }`, - onclick: () => (positivePrompt.val = !positivePrompt.val), - }, - div(() => (positivePrompt.val ? "Positive" : "Negative")) + div(() => (showSidebar.val ? "Hide UI" : "Show UI")) + ), + button( + { + class: () => "btn btn-neutral flex flex-row normal-case rounded-md", + onclick: () => { + enableAutoSegment.val = !enableAutoSegment.val; + drawSegment(getClicks()); + }, + }, + () => (enableAutoSegment.val ? "Auto Segment On" : "Auto Segment Off") + ), + button( + { + class: () => "btn btn-neutral flex flex-row normal-case rounded-md", + onclick: () => { + enableBackgroundRemover.val = !enableBackgroundRemover.val; + setRemoveBackgroundNode(); + }, + }, + () => + enableBackgroundRemover.val + ? "Background Remover On" + : "Background Remover Off" + ), + button( + { + class: () => + `btn btn-neutral flex flex-row normal-case rounded-md ${ + isMobileDevice() ? "" : "hidden" + }`, + onclick: () => (positivePrompt.val = !positivePrompt.val), + }, + div(() => (positivePrompt.val ? "Positive" : "Negative")) + ) ), div( { @@ -617,7 +646,10 @@ export function LayerEditor() { style: () => `width: ${imageContainerSize.val.width}px; height: ${imageContainerSize.val.height}px;`, }, - ...imagePrompts.val?.map((point) => { + ...(enableAutoSegment.val + ? imagePrompts.val + : imagePrompts.val?.filter((click) => !click.isAuto) + ).map((point) => { return button({ style: () => `left: ${ diff --git a/js/tw-styles.css b/js/tw-styles.css index e342b35..63baa6d 100644 --- a/js/tw-styles.css +++ b/js/tw-styles.css @@ -1,7 +1,7 @@ @import url('https://fonts.googleapis.com/css2?family=Gabarito&display=swap'); /* -! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com */ /* @@ -501,7 +501,7 @@ html{ :root{ color-scheme: light; --in: 0.7206 0.191 231.6; - --su: 64.8% 0.150 160; + --su: 0.7441 0.213 164.75; --wa: 0.8471 0.199 83.87; --er: 0.7176 0.221 22.18; --pc: 0.89824 0.06192 275.75; @@ -870,7 +870,8 @@ html{ flex-grow: 1; --tw-content: ''; content: var(--tw-content); - background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } .dropdown{ @@ -1151,8 +1152,10 @@ html{ .menu :where(li ul){ position: relative; white-space: nowrap; - margin-inline-start: 1rem; - padding-inline-start: 0.5rem; + -webkit-margin-start: 1rem; + margin-inline-start: 1rem; + -webkit-padding-start: 0.5rem; + padding-inline-start: 0.5rem; } .menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), @@ -1482,7 +1485,8 @@ details.collapse summary::-webkit-details-marker{ :where(.collapse > input[type="radio"]){ width: 100%; padding: 1rem; - padding-inline-end: 3rem; + -webkit-padding-end: 3rem; + padding-inline-end: 3rem; min-height: 3.75rem; transition: background-color 0.2s ease-out; } @@ -1576,7 +1580,8 @@ details.collapse summary::-webkit-details-marker{ .join > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-inline-start: -1px; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .link:focus{ @@ -2078,7 +2083,8 @@ details.collapse summary::-webkit-details-marker{ .join.join-horizontal > :where(*:not(:first-child)){ margin-top: 0px; margin-bottom: 0px; - margin-inline-start: -1px; + -webkit-margin-start: -1px; + margin-inline-start: -1px; } .modal-top :where(.modal-box){ @@ -2168,10 +2174,6 @@ details.collapse summary::-webkit-details-marker{ left: 50%; } -.left-2{ - left: 0.5rem; -} - .left-28{ left: 7rem; } @@ -2180,10 +2182,6 @@ details.collapse summary::-webkit-details-marker{ left: 1rem; } -.left-52{ - left: 13rem; -} - .right-0{ right: 0px; } @@ -2394,6 +2392,10 @@ details.collapse summary::-webkit-details-marker{ align-items: center; } +.justify-start{ + justify-content: flex-start; +} + .justify-center{ justify-content: center; } From 02be90f8ffc48654cd25dab0e3627e69899b91aa Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 12:45:38 +0800 Subject: [PATCH 102/127] feat: avatar generation endpoint --- routes.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/routes.py b/routes.py index 038f731..f9db3f7 100644 --- a/routes.py +++ b/routes.py @@ -12,6 +12,8 @@ import re import base64 from PIL import Image import io +import time +import execution load_dotenv() @@ -102,11 +104,17 @@ async def post_sam_model(request): "Content-type": "application/json", "Accept": "application/json", }, - data=json.dumps({ - "image": image, - }), + data=json.dumps( + { + "image": image, + } + ), ).json() - emb, input_size, original_size = res["emb"], res["input_size"], res["original_size"] + emb, input_size, original_size = ( + res["emb"], + res["input_size"], + res["original_size"], + ) emb = np.array(emb).astype(np.float32) np.save(emb_filename, emb) with open(output_json_filename, "w") as f: @@ -135,6 +143,102 @@ async def post_sam_model(request): return web.json_response({}) +def save_image(image): + input_folder = folder_paths.get_input_directory() + name, extension = os.path.splitext(image.filename) + save_name = f"{name}{extension}" + i = 1 + while os.path.exists(f"{input_folder}/{save_name}"): + save_name = f"{name}_{i}{extension}" + i += 1 + + with open(f"{input_folder}/{save_name}", "wb") as f: + f.write(image.file.read()) + + return save_name + + +def post_prompt(json_data): + prompt_server = server.PromptServer.instance + json_data = prompt_server.trigger_on_prompt(json_data) + + if "number" in json_data: + number = float(json_data["number"]) + else: + number = prompt_server.number + if "front" in json_data: + if json_data["front"]: + number = -number + + prompt_server.number += 1 + + if "prompt" in json_data: + prompt = json_data["prompt"] + valid = execution.validate_prompt(prompt) + extra_data = {} + if "extra_data" in json_data: + extra_data = json_data["extra_data"] + + if "client_id" in json_data: + extra_data["client_id"] = json_data["client_id"] + if valid[0]: + prompt_id = str(uuid.uuid4()) + outputs_to_execute = valid[2] + prompt_server.prompt_queue.put( + (number, prompt_id, prompt, extra_data, outputs_to_execute) + ) + response = { + "prompt_id": prompt_id, + "number": number, + "node_errors": valid[3], + } + return web.json_response(response) + else: + print("invalid prompt:", valid[1]) + return web.json_response( + {"error": valid[1], "node_errors": valid[3]}, status=400 + ) + else: + return web.json_response({"error": "no prompt", "node_errors": []}, status=400) + + +def get_avatar_file(outputs): + for node_id, output in outputs.items(): + if "gltfFilename" in output: + avatar_filename = output["gltfFilename"][0] + with open( + f"{folder_paths.get_output_directory()}/{avatar_filename}", "rb" + ) as f: + return f.read() + + +with open( + os.path.join( + os.path.dirname(__file__), "workflow_templates/api/avatar_generation_api.json" + ) +) as f: + api_prompt_string = "\n".join(f.readlines()) + + +@server.PromptServer.instance.routes.post("/avatar_generation") +async def post_prompt_block(request): + prompt_server = server.PromptServer.instance + post = await request.post() + image = post.get("image") + image_name = save_image(image) + + api_prompt = json.loads(api_prompt_string.replace("IMAGE_REFERENCE", image_name)) + json_data = {"prompt": api_prompt} + res = post_prompt(json_data) + prompt_id = json.loads(res.text)["prompt_id"] + while True: + history = prompt_server.prompt_queue.get_history(prompt_id=prompt_id) + if history: + file = get_avatar_file(history[prompt_id]["outputs"]) + return web.Response(body=file) + time.sleep(0.5) + + # @server.PromptServer.instance.routes.get("/get_default_workflow") # async def get_default_workflow(request): # # json_link = "https://cdn.discordapp.com/attachments/1119102674437156984/1172255632586448987/workflow_boy_2_1.json?ex=655fa722&is=654d3222&hm=463fa6a3c6ea60f7471196ff45382c729d3b856e86282f905d37a0398711860e&" # YP workflow From c3ce88fd6f42a319f35d608dc0d688113c29e280 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 13:14:18 +0800 Subject: [PATCH 103/127] feat: custom workflow --- routes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/routes.py b/routes.py index f9db3f7..e7ad1e4 100644 --- a/routes.py +++ b/routes.py @@ -217,7 +217,7 @@ with open( os.path.dirname(__file__), "workflow_templates/api/avatar_generation_api.json" ) ) as f: - api_prompt_string = "\n".join(f.readlines()) + default_workflow = "\n".join(f.readlines()) @server.PromptServer.instance.routes.post("/avatar_generation") @@ -227,9 +227,10 @@ async def post_prompt_block(request): image = post.get("image") image_name = save_image(image) - api_prompt = json.loads(api_prompt_string.replace("IMAGE_REFERENCE", image_name)) - json_data = {"prompt": api_prompt} - res = post_prompt(json_data) + workflow = post.get("workflow") + workflow = default_workflow if workflow is None else workflow + api_prompt = json.loads(workflow.replace("IMAGE_REFERENCE", image_name)) + res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] while True: history = prompt_server.prompt_queue.get_history(prompt_id=prompt_id) From 9d848594f6f0443391ac5ba0e623c9778227c994 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 13:15:13 +0800 Subject: [PATCH 104/127] chore: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 078a9b6..63591ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Edit at https://www.toptal.com/developers/gitignore?templates=node,python,react js/output.css +*.task ### Node ### # Logs From 083e5784cfe994cefcd9a186f11342f3164274fe Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 13:46:31 +0800 Subject: [PATCH 105/127] feat: upload avatar --- routes.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/routes.py b/routes.py index e7ad1e4..fc71a46 100644 --- a/routes.py +++ b/routes.py @@ -212,6 +212,35 @@ def get_avatar_file(outputs): return f.read() +def upload_avatar_file(outputs): + file = get_avatar_file(outputs) + response = requests.get("https://labs.avatech.ai/api/share") + labData = response.json() + modelId = labData["modelId"] + + # upload model + headers = { + "x-amz-acl": "public-read", + "Content-Type": "model/gltf-binary", + "Content-Length": str(len(file)), + } + requests.put(labData["url"], headers=headers, data=file) + + # send notification + webhook_url = os.getenv("DISCORD_WEBHOOK_URL") + data = { + "username": "Avabot", + "avatar_url": "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png", + "content": "[API Call] New register!", + } + headers = { + "Content-Type": "application/json", + } + response = requests.post(webhook_url, headers=headers, data=json.dumps(data)) + + return modelId + + with open( os.path.join( os.path.dirname(__file__), "workflow_templates/api/avatar_generation_api.json" @@ -235,8 +264,11 @@ async def post_prompt_block(request): while True: history = prompt_server.prompt_queue.get_history(prompt_id=prompt_id) if history: - file = get_avatar_file(history[prompt_id]["outputs"]) - return web.Response(body=file) + # file = get_avatar_file(history[prompt_id]["outputs"]) + # return web.Response(body=file) + + modelId = upload_avatar_file(history[prompt_id]["outputs"]) + return web.json_response({"id": modelId}, status=200) time.sleep(0.5) From 654fdb2a906771081389c18d4cd4a6d3224bb779 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 16:08:14 +0800 Subject: [PATCH 106/127] fix: template seed --- routes.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/routes.py b/routes.py index fc71a46..3e571c9 100644 --- a/routes.py +++ b/routes.py @@ -14,6 +14,7 @@ from PIL import Image import io import time import execution +import random load_dotenv() @@ -241,6 +242,12 @@ def upload_avatar_file(outputs): return modelId +def randomSeed(num_digits=15): + range_start = 10 ** (num_digits - 1) + range_end = (10**num_digits) - 1 + return random.randint(range_start, range_end) + + with open( os.path.join( os.path.dirname(__file__), "workflow_templates/api/avatar_generation_api.json" @@ -258,7 +265,11 @@ async def post_prompt_block(request): workflow = post.get("workflow") workflow = default_workflow if workflow is None else workflow - api_prompt = json.loads(workflow.replace("IMAGE_REFERENCE", image_name)) + api_prompt = json.loads( + workflow.replace("IMAGE_REFERENCE", image_name).replace( + "SEED", str(randomSeed()) + ) + ) res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] while True: From 5a0edbf378d66756fe13918036e3002f748f7e8d Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 16:48:42 +0800 Subject: [PATCH 107/127] fix: save image node not found --- js/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/index.js b/js/index.js index ce6e85c..6ec8753 100644 --- a/js/index.js +++ b/js/index.js @@ -246,10 +246,7 @@ function getInputWidgetValue(node, inputIndex, widgetName) { /** @type {LGraphNode} */ let nodea = graph._nodes_by_id[targetLink.origin_id]; - while ( - nodea.type === "Reroute" || - nodea.type === "Image Rembg (Remove Background)" - ) { + while (nodea.type === "Reroute") { nodea = nodea.getInputNode(0); } From 901b22c485f8616cb32c25c77bc7716a3b3d6d8a Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 27 Nov 2023 17:30:55 +0800 Subject: [PATCH 108/127] tweak --- js/LayerEditor.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 7845a04..c1f2e87 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -59,7 +59,7 @@ const layerMapping = { positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, + positiveScale: 0.25, negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, }, @@ -69,7 +69,7 @@ const layerMapping = { positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, + positiveScale: 0.25, negativeScale: 0.5, indices: FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, }, @@ -96,11 +96,11 @@ const layerMapping = { face: { useMiddle: false, positiveOffsetX: 0, - positiveOffsetY: 40, + positiveOffsetY: 60, negativeOffsetX: 0, - negativeOffsetY: 60, - positiveScale: 0.2, - negativeScale: 0.6, + negativeOffsetY: 0, + positiveScale: 0.5, + negativeScale: 0, indices: FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, }, mouth: { From d356ee5b4ab00525f7125e61217fa2fbac9a5550 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 28 Nov 2023 15:25:07 +0800 Subject: [PATCH 109/127] feat: add pose landmarker in backend --- __init__.py | 12 ++++++++--- sam/sam_multilayer.py | 48 +++++++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/__init__.py b/__init__.py index 5359d3d..b34764f 100644 --- a/__init__.py +++ b/__init__.py @@ -84,19 +84,25 @@ def download_sam_model(): download_sam_model() -def download_face_landmarker(): +def download_face_and_pose_landmarker(): model_dir = os.path.join(ag_path, "mediapipe_models") if not os.path.isdir(model_dir): os.makedirs(model_dir) model_path = os.path.join(model_dir, "face_landmarker.task") if not os.path.isfile(model_path): - print("Downloading mediapipe model...") + print("Downloading face landmarker model...") url = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task" download_model(url, model_path) + model_path = os.path.join(model_dir, "pose_landmarker_full.task") + if not os.path.isfile(model_path): + print("Downloading pose landmarker model...") + url = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_full/float16/latest/pose_landmarker_full.task" + download_model(url, model_path) -download_face_landmarker() + +download_face_and_pose_landmarker() paths = ["blender", "sam"] files = [] diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index ce036ab..2108542 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -13,10 +13,13 @@ from math import sqrt BaseOptions = mp.tasks.BaseOptions FaceLandmarker = mp.tasks.vision.FaceLandmarker FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions +PoseLandmarker = mp.tasks.vision.PoseLandmarker +PoseLandmarkerOptions = mp.tasks.vision.PoseLandmarkerOptions VisionRunningMode = mp.tasks.vision.RunningMode global_predictor = None face_landmarker = None +pose_landmarker = None # For auto-segmentation layerMapping = { @@ -118,22 +121,32 @@ class SAMMultiLayer: FUNCTION = "load_image" def load_models(self, ckpt, model_type): - global global_predictor, face_landmarker + global global_predictor, face_landmarker, pose_landmarker ckpt = folder_paths.get_full_path("sams", ckpt) sam = sam_model_registry[model_type](checkpoint=ckpt) # .to("cuda") global_predictor = SamPredictor(sam) - model_path = os.path.join(os.path.dirname(__file__), "../mediapipe_models/face_landmarker.task") - options = FaceLandmarkerOptions( - base_options=BaseOptions(model_asset_path=model_path), + face_landmarker_model_path = os.path.join( + os.path.dirname(__file__), "../mediapipe_models/face_landmarker.task" + ) + face_landmarker_options = FaceLandmarkerOptions( + base_options=BaseOptions(model_asset_path=face_landmarker_model_path), running_mode=VisionRunningMode.IMAGE, ) - face_landmarker = FaceLandmarker.create_from_options(options) + face_landmarker = FaceLandmarker.create_from_options(face_landmarker_options) - return global_predictor, face_landmarker + pose_landmarker_model_path = os.path.join( + os.path.dirname(__file__), "../mediapipe_models/pose_landmarker_full.task" + ) + pose_landmarker_options = PoseLandmarkerOptions( + base_options=BaseOptions(model_asset_path=pose_landmarker_model_path), + running_mode=VisionRunningMode.IMAGE, + ) + pose_landmarker = PoseLandmarker.create_from_options(pose_landmarker_options) + return global_predictor, face_landmarker, pose_landmarker - def auto_segment(self, image, face_landmarks): + def auto_segment(self, image, face_landmarks, pose_landmarks): H, W, C = image.shape imagePromptsMulti = {} boxesMulti = {} @@ -227,15 +240,25 @@ class SAMMultiLayer: ) boxesMulti[key] = box + if pose_landmarks is not None: + breathX = ((pose_landmarks[11].x + pose_landmarks[12].x) / 2) * W + breathY = ((pose_landmarks[11].y + pose_landmarks[12].y) / 2) * H + imagePromptsMulti["breath"] = [{"x": breathX, "y": breathY, "label": 1}] + return imagePromptsMulti, boxesMulti def detect_face(self, np_image): - global face_landmarker + global face_landmarker, pose_landmarker mp_image = mp.Image( image_format=mp.ImageFormat.SRGB, data=(np_image * 255).astype(np.uint8) ) - face_landmarks = face_landmarker.detect(mp_image).face_landmarks[0] - imagePromptsMulti, boxesMulti = self.auto_segment(np_image, face_landmarks) + face_landmarks = face_landmarker.detect(mp_image).face_landmarks + face_landmarks = face_landmarks[0] if len(face_landmarks) > 0 else None + pose_landmarks = pose_landmarker.detect(mp_image).pose_landmarks + pose_landmarks = pose_landmarks[0] if len(pose_landmarks) > 0 else None + imagePromptsMulti, boxesMulti = self.auto_segment( + np_image, face_landmarks, pose_landmarks + ) return imagePromptsMulti, boxesMulti @@ -263,10 +286,9 @@ class SAMMultiLayer: # Frontend uploads clicks coordinates to backend => backend runs SAM and passes the segments to next nodes model_type = re.findall(r"vit_[lbh]", ckpt)[0] - global global_predictor, face_landmarker + global global_predictor if global_predictor is None: - global_predictor, face_landmarker = self.load_models(ckpt, model_type) - print(face_landmarker) + global_predictor, _, _ = self.load_models(ckpt, model_type) if image.shape[3] == 4: image = image[:, :, :, :3] From db33d7bfd776c9ce496db1fba96938af248b95f8 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 28 Nov 2023 15:51:46 +0800 Subject: [PATCH 110/127] feat: negative points for breath --- js/LayerEditor.js | 16 +++++++++++++--- sam/sam_multilayer.py | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index c1f2e87..56cc300 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -256,12 +256,22 @@ export async function autoSegment() { }); const poseLandmarks = poseLandmarker.detect(image).landmarks[0]; - const breathX = + const positiveBreathX = ((poseLandmarks[11].x + poseLandmarks[12].x) / 2) * imageSize.val.width; - const breathY = + const positiveBreathY = ((poseLandmarks[11].y + poseLandmarks[12].y) / 2) * imageSize.val.height; + const negativeBreathX1 = poseLandmarks[0].x * imageSize.val.width; + const negativeBreathY1 = poseLandmarks[0].y * imageSize.val.height; + const negativeBreathX2 = poseLandmarks[9].x * imageSize.val.width; + const negativeBreathY2 = poseLandmarks[9].y * imageSize.val.height; + const negativeBreathX3 = poseLandmarks[10].x * imageSize.val.width; + const negativeBreathY3 = poseLandmarks[10].y * imageSize.val.height; + imagePromptsMulti.val["breath"] = [ - { x: breathX, y: breathY, label: 1, isAuto: true }, + { x: positiveBreathX, y: positiveBreathY, label: 1, isAuto: true }, + { x: negativeBreathX1, y: negativeBreathY1, label: 0, isAuto: true }, + { x: negativeBreathX2, y: negativeBreathY2, label: 0, isAuto: true }, + { x: negativeBreathX3, y: negativeBreathY3, label: 0, isAuto: true }, ]; imagePrompts.val = imagePromptsMulti.val[selectedLayer.val]; segmented.val = true; diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 2108542..ea5eae5 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -241,9 +241,24 @@ class SAMMultiLayer: boxesMulti[key] = box if pose_landmarks is not None: - breathX = ((pose_landmarks[11].x + pose_landmarks[12].x) / 2) * W - breathY = ((pose_landmarks[11].y + pose_landmarks[12].y) / 2) * H - imagePromptsMulti["breath"] = [{"x": breathX, "y": breathY, "label": 1}] + positiveBreathX = ( + (pose_landmarks[11].x + pose_landmarks[12].x) / 2 + ) * W + positiveBreathY = ( + (pose_landmarks[11].y + pose_landmarks[12].y) / 2 + ) * H + negativeBreathX1 = pose_landmarks[0].x * W + negativeBreathY1 = pose_landmarks[0].y * H + negativeBreathX2 = pose_landmarks[9].x * W + negativeBreathY2 = pose_landmarks[9].y * H + negativeBreathX3 = pose_landmarks[10].x * W + negativeBreathY3 = pose_landmarks[10].y * H + imagePromptsMulti["breath"] = [ + {"x": positiveBreathX, "y": positiveBreathY, "label": 1}, + {"x": negativeBreathX1, "y": negativeBreathY1, "label": 0}, + {"x": negativeBreathX2, "y": negativeBreathY2, "label": 0}, + {"x": negativeBreathX3, "y": negativeBreathY3, "label": 0}, + ] return imagePromptsMulti, boxesMulti From 53377929d617f441decc37f7981fa0819cecabc5 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 28 Nov 2023 16:04:11 +0800 Subject: [PATCH 111/127] fix: backend segment --- sam/sam_multilayer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index ea5eae5..224ac9d 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -348,9 +348,7 @@ class SAMMultiLayer: continue points = ( - item + imagePromptsMulti[key] - if key in imagePromptsMulti - else item + imagePromptsMulti[key] if key in imagePromptsMulti else item ) point_coords = np.array([[p["x"], p["y"]] for p in points]) point_labels = np.array([p["label"] for p in points]) From c7a487c2e798b087dba119927f78d7b3b3fe51d0 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 28 Nov 2023 17:04:19 +0800 Subject: [PATCH 112/127] chore: update api template --- routes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes.py b/routes.py index 3e571c9..d0361c8 100644 --- a/routes.py +++ b/routes.py @@ -250,7 +250,8 @@ def randomSeed(num_digits=15): with open( os.path.join( - os.path.dirname(__file__), "workflow_templates/api/avatar_generation_api.json" + os.path.dirname(__file__), + "workflow_templates/api/avatar_generation_mask_api.json", ) ) as f: default_workflow = "\n".join(f.readlines()) @@ -279,6 +280,7 @@ async def post_prompt_block(request): # return web.Response(body=file) modelId = upload_avatar_file(history[prompt_id]["outputs"]) + print("model id", modelId) return web.json_response({"id": modelId}, status=200) time.sleep(0.5) From aa3f5ee61a39d3eca954be57ba3c4a401e9cc39d Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 29 Nov 2023 12:59:32 +0800 Subject: [PATCH 113/127] feat: inner and outer lips detection --- js/LayerEditor.js | 24 ++++++++++++++++-------- sam/sam_multilayer.py | 18 ++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/js/LayerEditor.js b/js/LayerEditor.js index 56cc300..41fe2f1 100644 --- a/js/LayerEditor.js +++ b/js/LayerEditor.js @@ -104,24 +104,32 @@ const layerMapping = { indices: FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, }, mouth: { - useMiddle: true, + useMiddle: false, positiveOffsetX: 0, positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, - negativeScale: 0, - indices: FaceLandmarker.FACE_LANDMARKS_LIPS, + positiveScale: -0.3, + negativeScale: 0.3, + // https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face + indices: [61, 37, 270, 91, 314].map((x) => ({ + start: x, + end: x, + })), }, mouth_in: { - useMiddle: true, + useMiddle: false, positiveOffsetX: 0, positiveOffsetY: 0, negativeOffsetX: 0, negativeOffsetY: 0, - positiveScale: 0, - negativeScale: 0, - indices: FaceLandmarker.FACE_LANDMARKS_LIPS, + positiveScale: -0.5, + negativeScale: 0.5, + // https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face + indices: [310, 88].map((x) => ({ + start: x, + end: x, + })), }, }; diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 224ac9d..6b6bc66 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -74,24 +74,26 @@ layerMapping = { "indices": mp.solutions.face_mesh.FACEMESH_FACE_OVAL, }, "mouth": { - "useMiddle": True, + "useMiddle": False, "positiveOffsetX": 0, "positiveOffsetY": 0, "negativeOffsetX": 0, "negativeOffsetY": 0, - "positiveScale": 0, - "negativeScale": 0, - "indices": mp.solutions.face_mesh.FACEMESH_LIPS, + "positiveScale": -0.3, + "negativeScale": 0.3, + # https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face + "indices": [[x, x] for x in [61, 37, 270, 91, 314]], }, "mouth_in": { - "useMiddle": True, + "useMiddle": False, "positiveOffsetX": 0, "positiveOffsetY": 0, "negativeOffsetX": 0, "negativeOffsetY": 0, - "positiveScale": 0, - "negativeScale": 0, - "indices": mp.solutions.face_mesh.FACEMESH_LIPS, + "positiveScale": -0.5, + "negativeScale": 0.5, + # https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face + "indices": [[x, x] for x in [310, 88]], }, } From 6193b69102f1e013323044075a207c3fa3a2fa97 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 1 Dec 2023 06:43:42 -0500 Subject: [PATCH 114/127] feat: UV modifier --- blender/ops_modify_uv.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 blender/ops_modify_uv.py diff --git a/blender/ops_modify_uv.py b/blender/ops_modify_uv.py new file mode 100644 index 0000000..86d3e7c --- /dev/null +++ b/blender/ops_modify_uv.py @@ -0,0 +1,24 @@ +import blender_node +from mesh_utils import genreate_mesh_from_texture, assign_texture + +class Object_UV_Modifier(blender_node.EditOps): + + EXTRA_INPUT_TYPES = { + "scale": ('FLOAT', {'default': 1, "display": "number", "step": 0.01}), + "texture_name": ('STRING', {'default': 'Texture', }) + } + + CUSTOM_NAME = "UV Modifier" + + def blender_process(self, bpy, BPY_OBJ, scale, texture_name): + import bmesh + + bm = bmesh.from_edit_mesh(BPY_OBJ.data) + + uv_layer = bm.loops.layers.uv.verify() + for f in bm.faces: + # move all of the UVs in this face up one UDIM tile + for l in f.loops: + l[uv_layer].uv = (l[uv_layer].uv[0], 0.998 if l[uv_layer].uv[1] == 1 else l[uv_layer].uv[1]) + + bmesh.update_edit_mesh(BPY_OBJ.data) \ No newline at end of file From cffb7cafcec43faa4e7e5cadbd839cef8ece8a01 Mon Sep 17 00:00:00 2001 From: Radionic Date: Tue, 5 Dec 2023 12:40:32 +0800 Subject: [PATCH 115/127] feat: support uploading mask in api --- routes.py | 36 +++++++++++++++++------------ sam/load_image_from_request.py | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 sam/load_image_from_request.py diff --git a/routes.py b/routes.py index d0361c8..0ddf618 100644 --- a/routes.py +++ b/routes.py @@ -125,7 +125,7 @@ async def post_sam_model(request): } json.dump(data, f) else: - sam = sam_model_registry[model_type](checkpoint=ckpt) + sam = sam_model_registry[model_type](checkpoint=ckpt).to("cuda") predictor = SamPredictor(sam) image_np = (image * 255).astype(np.uint8) @@ -144,14 +144,16 @@ async def post_sam_model(request): return web.json_response({}) -def save_image(image): +def save_image(image, save_name=None): input_folder = folder_paths.get_input_directory() name, extension = os.path.splitext(image.filename) - save_name = f"{name}{extension}" - i = 1 - while os.path.exists(f"{input_folder}/{save_name}"): - save_name = f"{name}_{i}{extension}" - i += 1 + + if save_name == None: + save_name = f"{name}{extension}" + i = 1 + while os.path.exists(f"{input_folder}/{save_name}"): + save_name = f"{name}_{i}{extension}" + i += 1 with open(f"{input_folder}/{save_name}", "wb") as f: f.write(image.file.read()) @@ -261,16 +263,22 @@ with open( async def post_prompt_block(request): prompt_server = server.PromptServer.instance post = await request.post() - image = post.get("image") - image_name = save_image(image) - workflow = post.get("workflow") workflow = default_workflow if workflow is None else workflow - api_prompt = json.loads( - workflow.replace("IMAGE_REFERENCE", image_name).replace( - "SEED", str(randomSeed()) - ) + + image = post.get("image") + image_path = save_image(image) + image_name, image_ext = os.path.splitext(image_path) + workflow = workflow.replace("IMAGE_REFERENCE", image_path).replace( + "SEED", str(randomSeed()) ) + for key, value in post.items(): + if key.startswith("mask_"): + mask_name = image_name + "_" + key.replace("mask_", "") + image_ext + mask_path = save_image(value, save_name=mask_name) + workflow = workflow.replace(key, mask_path) + + api_prompt = json.loads(workflow) res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] while True: diff --git a/sam/load_image_from_request.py b/sam/load_image_from_request.py new file mode 100644 index 0000000..a327d54 --- /dev/null +++ b/sam/load_image_from_request.py @@ -0,0 +1,41 @@ +import folder_paths +from PIL import Image, ImageOps +import numpy as np +import torch + +class LoadImageFromRequest: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "name": ( + "STRING", + {"multiline": False, "default": "face.png"}, + ), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + + FUNCTION = "run" + + CATEGORY = "image" + + def run(self, image, name): + try: + image_path = folder_paths.get_annotated_filepath(name) + i = Image.open(image_path) + i = ImageOps.exif_transpose(i) + image = i.convert("RGB") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + return [image] + except: + return [image] + + +NODE_CLASS_MAPPINGS = {"LoadImageFromRequest": LoadImageFromRequest} + +NODE_DISPLAY_NAME_MAPPINGS = {"LoadImageFromRequest": "Load Image From Request"} From 51ce8ba957b76cb89e449c1674f2909e130aa112 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Tue, 5 Dec 2023 17:06:11 +0800 Subject: [PATCH 116/127] remove templates and add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 63591ee..1317219 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Created by https://www.toptal.com/developers/gitignore/api/node,python,react # Edit at https://www.toptal.com/developers/gitignore?templates=node,python,react +workflow_templates/ js/output.css *.task From 5273e07eacd790c4cec2e87417794d26289dc20e Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 6 Dec 2023 15:52:16 +0800 Subject: [PATCH 117/127] feat: support base image as input --- routes.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/routes.py b/routes.py index 0ddf618..d4b1706 100644 --- a/routes.py +++ b/routes.py @@ -253,7 +253,7 @@ def randomSeed(num_digits=15): with open( os.path.join( os.path.dirname(__file__), - "workflow_templates/api/avatar_generation_mask_api.json", + "workflow_templates/api/avatar_generation_mask_api_v2.json", ) ) as f: default_workflow = "\n".join(f.readlines()) @@ -266,18 +266,26 @@ async def post_prompt_block(request): workflow = post.get("workflow") workflow = default_workflow if workflow is None else workflow - image = post.get("image") - image_path = save_image(image) - image_name, image_ext = os.path.splitext(image_path) - workflow = workflow.replace("IMAGE_REFERENCE", image_path).replace( - "SEED", str(randomSeed()) - ) + ref_image = post.get("ref_image") + base_image = post.get("base_image") + if ref_image is not None: + image_path = save_image(ref_image) + image_name, image_ext = os.path.splitext(image_path) + workflow = workflow.replace("reference_image", image_path) + elif base_image is not None: + image_path = save_image(base_image) + image_name, image_ext = os.path.splitext(image_path) + workflow = workflow.replace("base_image", image_path) + workflow = workflow.replace("reference_image", image_path) # TMP + for key, value in post.items(): if key.startswith("mask_"): mask_name = image_name + "_" + key.replace("mask_", "") + image_ext mask_path = save_image(value, save_name=mask_name) workflow = workflow.replace(key, mask_path) + workflow = workflow.replace("SEED", str(randomSeed())) + api_prompt = json.loads(workflow) res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] From 3d25c64b7d31b5d3a71b062715d1229327799012 Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 7 Dec 2023 13:05:59 +0800 Subject: [PATCH 118/127] feat: skip generation part if base image is provided --- routes.py | 18 ++++++++++++++++-- sam/load_image_from_request.py | 6 ++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/routes.py b/routes.py index d4b1706..cb2998c 100644 --- a/routes.py +++ b/routes.py @@ -276,7 +276,7 @@ async def post_prompt_block(request): image_path = save_image(base_image) image_name, image_ext = os.path.splitext(image_path) workflow = workflow.replace("base_image", image_path) - workflow = workflow.replace("reference_image", image_path) # TMP + workflow = workflow.replace("reference_image", image_path) # TMP for key, value in post.items(): if key.startswith("mask_"): @@ -285,8 +285,22 @@ async def post_prompt_block(request): workflow = workflow.replace(key, mask_path) workflow = workflow.replace("SEED", str(randomSeed())) - api_prompt = json.loads(workflow) + + # skip generation part if base_image is provided + if base_image is not None: + for value in api_prompt.values(): + if ( + value["class_type"] == "LoadImageFromRequest" + and value["inputs"]["name"] == image_path + ): + del value["inputs"]["image"] + elif ( + value["class_type"] == "PreviewImage" + or value["class_type"] == "SaveImage" + ): + del value["inputs"]["images"] + res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] while True: diff --git a/sam/load_image_from_request.py b/sam/load_image_from_request.py index a327d54..c4232cd 100644 --- a/sam/load_image_from_request.py +++ b/sam/load_image_from_request.py @@ -8,12 +8,14 @@ class LoadImageFromRequest: def INPUT_TYPES(s): return { "required": { - "image": ("IMAGE",), "name": ( "STRING", {"multiline": False, "default": "face.png"}, ), }, + "optional": { + "image": ("IMAGE",), + } } RETURN_TYPES = ("IMAGE",) @@ -23,7 +25,7 @@ class LoadImageFromRequest: CATEGORY = "image" - def run(self, image, name): + def run(self, name, image=None): try: image_path = folder_paths.get_annotated_filepath(name) i = Image.open(image_path) From a7aea4dbee8f12a941a4f1221358651c3c9d2dba Mon Sep 17 00:00:00 2001 From: Radionic Date: Thu, 7 Dec 2023 18:38:12 +0800 Subject: [PATCH 119/127] chore: comment out skipping generation code --- routes.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/routes.py b/routes.py index cb2998c..0d923c2 100644 --- a/routes.py +++ b/routes.py @@ -288,18 +288,18 @@ async def post_prompt_block(request): api_prompt = json.loads(workflow) # skip generation part if base_image is provided - if base_image is not None: - for value in api_prompt.values(): - if ( - value["class_type"] == "LoadImageFromRequest" - and value["inputs"]["name"] == image_path - ): - del value["inputs"]["image"] - elif ( - value["class_type"] == "PreviewImage" - or value["class_type"] == "SaveImage" - ): - del value["inputs"]["images"] + # if base_image is not None: + # for value in api_prompt.values(): + # if ( + # value["class_type"] == "LoadImageFromRequest" + # and value["inputs"]["name"] == image_path + # ): + # del value["inputs"]["image"] + # elif ( + # value["class_type"] == "PreviewImage" + # or value["class_type"] == "SaveImage" + # ): + # del value["inputs"]["images"] res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] From 8d0bf8f458c9b36ce718502893d5268d9b67cb0c Mon Sep 17 00:00:00 2001 From: Radionic Date: Fri, 8 Dec 2023 12:19:36 +0800 Subject: [PATCH 120/127] feat: switch workflow based on name --- routes.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/routes.py b/routes.py index 0d923c2..e28fdc3 100644 --- a/routes.py +++ b/routes.py @@ -250,21 +250,31 @@ def randomSeed(num_digits=15): return random.randint(range_start, range_end) -with open( - os.path.join( - os.path.dirname(__file__), - "workflow_templates/api/avatar_generation_mask_api_v2.json", - ) -) as f: - default_workflow = "\n".join(f.readlines()) +def load_workflow(workflow_name): + with open( + os.path.join( + os.path.dirname(__file__), + f"workflow_templates/api/{workflow_name}.json", + ) + ) as f: + return "\n".join(f.readlines()) + + +default_workflow = load_workflow("avatar_generation_mask_api_v11(FaceToon)") @server.PromptServer.instance.routes.post("/avatar_generation") async def post_prompt_block(request): prompt_server = server.PromptServer.instance post = await request.post() - workflow = post.get("workflow") - workflow = default_workflow if workflow is None else workflow + uploaded_workflow = post.get("workflow") + workflow_name = post.get("workflow_name") + if uploaded_workflow is not None: + workflow = uploaded_workflow + elif workflow_name is not None: + workflow = load_workflow(workflow_name) + else: + workflow = default_workflow ref_image = post.get("ref_image") base_image = post.get("base_image") From f8b220049b56039b180b161b2524a4f8bc4ddb3c Mon Sep 17 00:00:00 2001 From: EdwinWong Date: Fri, 8 Dec 2023 12:24:23 +0800 Subject: [PATCH 121/127] chore: update name --- routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes.py b/routes.py index e28fdc3..d20c0d4 100644 --- a/routes.py +++ b/routes.py @@ -281,12 +281,12 @@ async def post_prompt_block(request): if ref_image is not None: image_path = save_image(ref_image) image_name, image_ext = os.path.splitext(image_path) - workflow = workflow.replace("reference_image", image_path) + workflow = workflow.replace("reference_image_avatech", image_path) elif base_image is not None: image_path = save_image(base_image) image_name, image_ext = os.path.splitext(image_path) workflow = workflow.replace("base_image", image_path) - workflow = workflow.replace("reference_image", image_path) # TMP + workflow = workflow.replace("reference_image_avatech", image_path) # TMP for key, value in post.items(): if key.startswith("mask_"): From d48a9e31fc301a8743e27b55a7dd1931cd99d5d3 Mon Sep 17 00:00:00 2001 From: EdwinWong Date: Fri, 8 Dec 2023 17:00:06 +0800 Subject: [PATCH 122/127] feat: api format --- js/index.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/js/index.js b/js/index.js index 6ec8753..6ee196e 100644 --- a/js/index.js +++ b/js/index.js @@ -736,6 +736,33 @@ function injectUIComponentToComfyuimenu() { localStorage.setItem("showPreview", showPreview.val); }; + const apiFormat = document.createElement("button"); + const a = document.createElement("a"); + apiFormat.textContent = "Save API Format (Avatech)"; + apiFormat.onclick = () => { + let filename = "workflow_api.json"; + filename = prompt("Save workflow (API) as:", filename); + if (!filename) return; + if (!filename.toLowerCase().endsWith(".json")) { + filename += ".json"; + } + app.graphToPrompt().then(p=>{ + console.log('fkfk'); + let json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string + json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"'); + const blob = new Blob([json], {type: "application/json"}); + const url = URL.createObjectURL(blob); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + a.remove(); + window.URL.revokeObjectURL(url); + }, 0); + }); + }; + const dropdown = document.createElement("div"); dropdown.textContent = "▼"; dropdown.className = "dropdownbtn"; @@ -844,6 +871,7 @@ function injectUIComponentToComfyuimenu() { menu.append(avatarPreview); menu.append(shareAvatar); + menu.append(apiFormat); shareAvatar.append(dropdown); } From 7abd8c604a2b6d00cfefba2d356b453165fec895 Mon Sep 17 00:00:00 2001 From: Radionic Date: Sat, 9 Dec 2023 13:35:04 +0800 Subject: [PATCH 123/127] fix: embedding id --- routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/routes.py b/routes.py index d20c0d4..8641ed5 100644 --- a/routes.py +++ b/routes.py @@ -294,6 +294,7 @@ async def post_prompt_block(request): mask_path = save_image(value, save_name=mask_name) workflow = workflow.replace(key, mask_path) + workflow = workflow.replace("embedding_id_avatech", image_path) workflow = workflow.replace("SEED", str(randomSeed())) api_prompt = json.loads(workflow) From 2d6e639af5c57da4244c298c11bbf4552c10c551 Mon Sep 17 00:00:00 2001 From: EdwinWong Date: Mon, 11 Dec 2023 16:14:43 +0800 Subject: [PATCH 124/127] fix: api format (embedding_id) --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 6ee196e..c22dab5 100644 --- a/js/index.js +++ b/js/index.js @@ -749,7 +749,7 @@ function injectUIComponentToComfyuimenu() { app.graphToPrompt().then(p=>{ console.log('fkfk'); let json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string - json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"'); + json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"').replace(/"embedding_id": "(\d+)"/g, '"embedding_id": "embedding_id_avatech"'); const blob = new Blob([json], {type: "application/json"}); const url = URL.createObjectURL(blob); a.href = url; From 240bc5d1b82b87b205caefff6e4eb06f62974d05 Mon Sep 17 00:00:00 2001 From: Radionic Date: Mon, 11 Dec 2023 16:24:37 +0800 Subject: [PATCH 125/127] fix: black background --- sam/load_image_from_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sam/load_image_from_request.py b/sam/load_image_from_request.py index c4232cd..f18107a 100644 --- a/sam/load_image_from_request.py +++ b/sam/load_image_from_request.py @@ -28,9 +28,9 @@ class LoadImageFromRequest: def run(self, name, image=None): try: image_path = folder_paths.get_annotated_filepath(name) - i = Image.open(image_path) - i = ImageOps.exif_transpose(i) - image = i.convert("RGB") + image = Image.open(image_path) + image = ImageOps.exif_transpose(image) + # image = image.convert("RGB") image = np.array(image).astype(np.float32) / 255.0 image = torch.from_numpy(image)[None,] return [image] From 420fbff0b9df6202c50c2c31c0b91cde62319a36 Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 20 Dec 2023 17:09:45 +0800 Subject: [PATCH 126/127] fix: embedding id --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index c22dab5..d188493 100644 --- a/js/index.js +++ b/js/index.js @@ -749,7 +749,7 @@ function injectUIComponentToComfyuimenu() { app.graphToPrompt().then(p=>{ console.log('fkfk'); let json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string - json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"').replace(/"embedding_id": "(\d+)"/g, '"embedding_id": "embedding_id_avatech"'); + json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"').replace(/"embedding_id": ".*"/g, '"embedding_id": "embedding_id_avatech"'); const blob = new Blob([json], {type: "application/json"}); const url = URL.createObjectURL(blob); a.href = url; From 41ea7fedaa6e5c32ced1217d512098beebd7a7f0 Mon Sep 17 00:00:00 2001 From: Radionic Date: Wed, 20 Dec 2023 17:32:44 +0800 Subject: [PATCH 127/127] fix: skip generation --- routes.py | 24 ++++++++++++------------ sam/sam_multilayer.py | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/routes.py b/routes.py index 8641ed5..1c0161f 100644 --- a/routes.py +++ b/routes.py @@ -299,18 +299,18 @@ async def post_prompt_block(request): api_prompt = json.loads(workflow) # skip generation part if base_image is provided - # if base_image is not None: - # for value in api_prompt.values(): - # if ( - # value["class_type"] == "LoadImageFromRequest" - # and value["inputs"]["name"] == image_path - # ): - # del value["inputs"]["image"] - # elif ( - # value["class_type"] == "PreviewImage" - # or value["class_type"] == "SaveImage" - # ): - # del value["inputs"]["images"] + if base_image is not None: + for value in api_prompt.values(): + if ( + value["class_type"] == "LoadImageFromRequest" + and value["inputs"]["name"] == image_path + ): + del value["inputs"]["image"] + elif ( + value["class_type"] == "PreviewImage" + or value["class_type"] == "SaveImage" + ): + value["inputs"] = {} res = post_prompt({"prompt": api_prompt}) prompt_id = json.loads(res.text)["prompt_id"] diff --git a/sam/sam_multilayer.py b/sam/sam_multilayer.py index 6b6bc66..3a7bfbd 100644 --- a/sam/sam_multilayer.py +++ b/sam/sam_multilayer.py @@ -280,7 +280,7 @@ class SAMMultiLayer: return imagePromptsMulti, boxesMulti def load_image(self, image, ckpt, embedding_id, image_prompts_json): - image_prompts = json.loads(image_prompts_json) + image_prompts = json.loads(image_prompts_json.replace("'", '"')) order_file = f"{self.output_dir}/segments_{embedding_id}/order.json" if os.path.exists(order_file): @@ -337,7 +337,7 @@ class SAMMultiLayer: imagePromptsMulti, boxesMulti = self.detect_face(image[0].numpy()) - image_prompts = json.loads(image_prompts_json) + image_prompts = json.loads(image_prompts_json.replace("'", '"')) result = [image_prompts] if isinstance(image_prompts, list):