Merge pull request #20 from space-nuko/properties-pane2
Properties pane
This commit is contained in:
21
README.md
21
README.md
@@ -6,6 +6,14 @@ This project is *still under construction* and many features are missing, be awa
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Download the latest release [here](https://nightly.link/space-nuko/ComfyBox/workflows/build-and-publish/master/ComfyBox-dist.zip) and extract it somewhere
|
||||||
|
2. Start the ComfyUI backend with `python main.py --enable-cors-header`
|
||||||
|
3. In the folder you extracted open the `run.bat`/`run.sh` script (requires Python 3 to be on your PATH). Alternatively you can serve the contents of the folder with a web server.
|
||||||
|
|
||||||
## NOTE
|
## NOTE
|
||||||
|
|
||||||
This frontend isn't compatible with regular ComfyUI's workflow format since extra metadata is saved like panel layout, so you'll have to spend a bit of time recreating them. This project also isn't compatible with regular ComfyUI's frontend extension format, but useful extensions can be integrated into this repo with some effort.
|
This frontend isn't compatible with regular ComfyUI's workflow format since extra metadata is saved like panel layout, so you'll have to spend a bit of time recreating them. This project also isn't compatible with regular ComfyUI's frontend extension format, but useful extensions can be integrated into this repo with some effort.
|
||||||
@@ -13,16 +21,19 @@ This frontend isn't compatible with regular ComfyUI's workflow format since extr
|
|||||||
## Proposed Features
|
## Proposed Features
|
||||||
- All the power of ComfyUI with more convenience on top
|
- All the power of ComfyUI with more convenience on top
|
||||||
- Autocreation of UI widgets from your workflow, quickly creating a personalized dashboard
|
- Autocreation of UI widgets from your workflow, quickly creating a personalized dashboard
|
||||||
- Custom widget and node types
|
- Arrange the UI however you like and attach custom classes/styles to each widget
|
||||||
- Look up queued and finished generations and their configs in realtime
|
- Custom widget types
|
||||||
|
- See the status of queued and finished generations and their configs in realtime
|
||||||
- Development with TypeScript
|
- Development with TypeScript
|
||||||
|
|
||||||
## Requirements
|
## Development
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
- `pnpm`
|
- `pnpm`
|
||||||
- An installation of vanilla [ComfyUI](https://github.com/comfyanonymous/ComfyUI) for the backend
|
- An installation of vanilla [ComfyUI](https://github.com/comfyanonymous/ComfyUI) for the backend
|
||||||
|
|
||||||
## Installation
|
### Installation
|
||||||
|
|
||||||
1. Clone the repo with submodules:
|
1. Clone the repo with submodules:
|
||||||
|
|
||||||
@@ -33,5 +44,5 @@ git clone https://github.com/space-nuko/ComfyBox --recursive
|
|||||||
2. `pnpm install`
|
2. `pnpm install`
|
||||||
4. `pnpm build:css`
|
4. `pnpm build:css`
|
||||||
5. `pnpm dev`
|
5. `pnpm dev`
|
||||||
6. Start ComfyUI as usual with `python main.py --enable-cors-header`
|
6. Start ComfyUI with `python main.py --enable-cors-header`
|
||||||
7. Visit `http://localhost:3000` in your browser
|
7. Visit `http://localhost:3000` in your browser
|
||||||
|
|||||||
11
bin/run.bat
Normal file
11
bin/run.bat
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
echo Starting ComfyBox.
|
||||||
|
echo Be sure you've started ComfyUI already using this command:
|
||||||
|
echo[
|
||||||
|
echo python main.py --enable-cors-header
|
||||||
|
echo[
|
||||||
|
echo Serving at http://localhost:8000
|
||||||
|
echo[
|
||||||
|
|
||||||
|
python -m http.server 8000
|
||||||
11
bin/run.sh
Normal file
11
bin/run.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
echo "Starting ComfyBox."
|
||||||
|
echo "Be sure you've started ComfyUI already using this command:"
|
||||||
|
echo ""
|
||||||
|
echo " python main.py --enable-cors-header"
|
||||||
|
echo ""
|
||||||
|
echo "Serving at http://localhost:8000"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
python -m http.server 8000
|
||||||
0
dist/.keep
vendored
0
dist/.keep
vendored
Submodule litegraph updated: 115bef46a5...6cbae97b3c
@@ -27,6 +27,7 @@
|
|||||||
"svelte-dnd-action": "^0.9.22",
|
"svelte-dnd-action": "^0.9.22",
|
||||||
"typescript": "^5.0.3",
|
"typescript": "^5.0.3",
|
||||||
"vite": "^4.3.1",
|
"vite": "^4.3.1",
|
||||||
|
"vite-plugin-static-copy": "^0.14.0",
|
||||||
"vite-tsconfig-paths": "^4.0.8",
|
"vite-tsconfig-paths": "^4.0.8",
|
||||||
"vitest": "^0.25.8"
|
"vitest": "^0.25.8"
|
||||||
},
|
},
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
"@litegraph-ts/nodes-basic": "workspace:*",
|
"@litegraph-ts/nodes-basic": "workspace:*",
|
||||||
"@litegraph-ts/nodes-events": "workspace:*",
|
"@litegraph-ts/nodes-events": "workspace:*",
|
||||||
"@litegraph-ts/nodes-math": "workspace:*",
|
"@litegraph-ts/nodes-math": "workspace:*",
|
||||||
|
"@litegraph-ts/nodes-strings": "workspace:*",
|
||||||
"@litegraph-ts/tsconfig": "workspace:*",
|
"@litegraph-ts/tsconfig": "workspace:*",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
|
|||||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@@ -43,6 +43,9 @@ importers:
|
|||||||
'@litegraph-ts/nodes-math':
|
'@litegraph-ts/nodes-math':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:litegraph/packages/nodes-math
|
version: link:litegraph/packages/nodes-math
|
||||||
|
'@litegraph-ts/nodes-strings':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:litegraph/packages/nodes-strings
|
||||||
'@litegraph-ts/tsconfig':
|
'@litegraph-ts/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:litegraph/packages/tsconfig
|
version: link:litegraph/packages/tsconfig
|
||||||
@@ -122,6 +125,9 @@ importers:
|
|||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.1
|
specifier: ^4.3.1
|
||||||
version: 4.3.1(sass@1.61.0)
|
version: 4.3.1(sass@1.61.0)
|
||||||
|
vite-plugin-static-copy:
|
||||||
|
specifier: ^0.14.0
|
||||||
|
version: 0.14.0(vite@4.3.1)
|
||||||
vite-tsconfig-paths:
|
vite-tsconfig-paths:
|
||||||
specifier: ^4.0.8
|
specifier: ^4.0.8
|
||||||
version: 4.0.8(typescript@5.0.3)(vite@4.3.1)
|
version: 4.0.8(typescript@5.0.3)(vite@4.3.1)
|
||||||
@@ -805,6 +811,22 @@ importers:
|
|||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.3.1
|
version: 4.3.1
|
||||||
|
|
||||||
|
litegraph/packages/nodes-strings:
|
||||||
|
dependencies:
|
||||||
|
'@litegraph-ts/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
devDependencies:
|
||||||
|
'@litegraph-ts/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../tsconfig
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.0.3
|
||||||
|
version: 5.0.3
|
||||||
|
vite:
|
||||||
|
specifier: ^4.2.1
|
||||||
|
version: 4.3.1
|
||||||
|
|
||||||
litegraph/packages/tsconfig: {}
|
litegraph/packages/tsconfig: {}
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
@@ -7065,6 +7087,19 @@ packages:
|
|||||||
vite: 4.3.1(sass@1.61.0)
|
vite: 4.3.1(sass@1.61.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/vite-plugin-static-copy@0.14.0(vite@4.3.1):
|
||||||
|
resolution: {integrity: sha512-RMFmb4czomcrsbQBiUZs9HcDGN3kxGvF+OrtkfTVocp12CuoUCuJQhcY26RK35A6KS4WasGzEwcYZqHMjkAvVw==}
|
||||||
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^3.0.0 || ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
chokidar: 3.5.3
|
||||||
|
fast-glob: 3.2.12
|
||||||
|
fs-extra: 11.1.1
|
||||||
|
picocolors: 1.0.0
|
||||||
|
vite: 4.3.1(sass@1.61.0)
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vite-tsconfig-paths@4.0.8(typescript@5.0.3)(vite@4.3.1):
|
/vite-tsconfig-paths@4.0.8(typescript@5.0.3)(vite@4.3.1):
|
||||||
resolution: {integrity: sha512-p04zH+Ey+NT78571x0pdX7nVRIJSlmKVvYryFglSWOK3Hc72eDL0+JJfbyQiugaIBApJkaEqbBQvqpsFZOSVGg==}
|
resolution: {integrity: sha512-p04zH+Ey+NT78571x0pdX7nVRIJSlmKVvYryFglSWOK3Hc72eDL0+JJfbyQiugaIBApJkaEqbBQvqpsFZOSVGg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4 } from "@litegraph-ts/core";
|
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode } from "@litegraph-ts/core";
|
||||||
import type ComfyApp from "./components/ComfyApp";
|
import type ComfyApp from "./components/ComfyApp";
|
||||||
import queueState from "./stores/queueState";
|
import queueState from "./stores/queueState";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import uiState from "./stores/uiState";
|
||||||
|
import layoutState from "./stores/layoutState";
|
||||||
|
|
||||||
export type SerializedGraphCanvasState = {
|
export type SerializedGraphCanvasState = {
|
||||||
offset: Vector2,
|
offset: Vector2,
|
||||||
@@ -101,7 +103,39 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private alignToGrid(node: LGraphNode, ctx: CanvasRenderingContext2D) {
|
||||||
|
const x = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
const y = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
|
||||||
|
const shiftX = x - node.pos[0];
|
||||||
|
let shiftY = y - node.pos[1];
|
||||||
|
|
||||||
|
let w, h;
|
||||||
|
if (node.flags.collapsed) {
|
||||||
|
w = node._collapsed_width;
|
||||||
|
h = LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
} else {
|
||||||
|
w = node.size[0];
|
||||||
|
h = node.size[1];
|
||||||
|
let titleMode = node.titleMode
|
||||||
|
if (titleMode !== TitleMode.TRANSPARENT_TITLE && titleMode !== TitleMode.NO_TITLE) {
|
||||||
|
h += LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
shiftY -= LiteGraph.NODE_TITLE_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const f = ctx.fillStyle;
|
||||||
|
ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
|
||||||
|
ctx.fillRect(shiftX, shiftY, w, h);
|
||||||
|
ctx.fillStyle = f;
|
||||||
|
}
|
||||||
|
|
||||||
override drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void {
|
override drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void {
|
||||||
|
if ((window as any)?.app?.shiftDown && this.node_dragged && node.id in this.selected_nodes) {
|
||||||
|
this.alignToGrid(node, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out inactive nodes
|
||||||
var editor_alpha = this.editor_alpha;
|
var editor_alpha = this.editor_alpha;
|
||||||
if (node.mode === NodeMode.NEVER) { // never
|
if (node.mode === NodeMode.NEVER) { // never
|
||||||
this.editor_alpha = 0.4;
|
this.editor_alpha = 0.4;
|
||||||
@@ -196,4 +230,23 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onSelectionChange(nodes: Record<number, LGraphNode>) {
|
||||||
|
const ls = get(layoutState)
|
||||||
|
ls.currentSelectionNodes = Object.values(nodes)
|
||||||
|
ls.currentSelection = []
|
||||||
|
layoutState.set(ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onNodeMoved(node: LGraphNode) {
|
||||||
|
if (super.onNodeMoved)
|
||||||
|
super.onNodeMoved(node);
|
||||||
|
|
||||||
|
if ((window as any)?.app?.shiftDown) {
|
||||||
|
// Ensure all selected nodes are realigned
|
||||||
|
for (const id in this.selected_nodes) {
|
||||||
|
this.selected_nodes[id].alignToGrid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,17 @@
|
|||||||
export let zIndex: number = 0;
|
export let zIndex: number = 0;
|
||||||
export let classes: string[] = [];
|
export let classes: string[] = [];
|
||||||
export let showHandles: boolean = false;
|
export let showHandles: boolean = false;
|
||||||
|
let attrsChanged: Writable<boolean> | null = null;
|
||||||
let children: IDragItem[] | null = null;
|
let children: IDragItem[] | null = null;
|
||||||
const flipDurationMs = 100;
|
const flipDurationMs = 100;
|
||||||
|
|
||||||
$: if (container) {
|
$: if (container) {
|
||||||
children = $layoutState.allItems[container.id].children;
|
children = $layoutState.allItems[container.id].children;
|
||||||
|
attrsChanged = container.attrsChanged
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
children = null;
|
children = null;
|
||||||
|
attrsChanged = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: any) {
|
||||||
@@ -38,26 +41,23 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if container && children}
|
{#if container && children}
|
||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
{@const edit = $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
|
{#key $attrsChanged}
|
||||||
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||||
|
class:hide-block={container.attrs.blockVariant === "hidden"}
|
||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
||||||
class:root-container={zIndex === 0}
|
class:root-container={zIndex === 0}
|
||||||
class:is-executing={container.isNodeExecuting}
|
class:is-executing={container.isNodeExecuting}
|
||||||
class:container-edit-outline={$uiState.uiEditMode === "widgets" && zIndex > 1}>
|
class:edit={edit}>
|
||||||
<Block>
|
<Block>
|
||||||
{#if container.attrs.showTitle}
|
{#if container.attrs.title !== ""}
|
||||||
<label for={String(container.id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
<label for={String(container.id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
||||||
<BlockTitle>
|
<BlockTitle>{container.attrs.title}</BlockTitle>
|
||||||
{#if $uiState.uiEditMode === "widgets"}
|
|
||||||
<input class="edit-title" bind:value={container.attrs.title} type="text" minlength="1" />
|
|
||||||
{:else}
|
|
||||||
{container.attrs.title}
|
|
||||||
{/if}
|
|
||||||
</BlockTitle>
|
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="v-pane"
|
<div class="v-pane"
|
||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
class:edit={edit}
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
items: children,
|
items: children,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
@@ -70,8 +70,12 @@
|
|||||||
on:finalize="{handleFinalize}"
|
on:finalize="{handleFinalize}"
|
||||||
>
|
>
|
||||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||||
|
{@const hidden = item?.attrs?.hidden}
|
||||||
<div class="animation-wrapper"
|
<div class="animation-wrapper"
|
||||||
animate:flip={{duration:flipDurationMs}}>
|
class:hidden={hidden}
|
||||||
|
animate:flip={{duration:flipDurationMs}}
|
||||||
|
style={item?.attrs?.flexGrow ? `flex-grow: ${item.attrs.flexGrow}` : ""}
|
||||||
|
>
|
||||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||||
@@ -79,11 +83,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{#if container.attrs.hidden && edit}
|
||||||
|
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||||
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
</Block>
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -97,6 +105,10 @@
|
|||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.edit) > .animation-wrapper.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.empty {
|
&.empty {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
border-color: var(--color-grey-400);
|
border-color: var(--color-grey-400);
|
||||||
@@ -134,10 +146,41 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
> :global(*) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> :global(.padded) {
|
||||||
|
padding: 10px 12px 0px 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:global(.block) {
|
:global(.block) {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit > :global(.block) {
|
||||||
|
border-color: var(--color-pink-500);
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: dashed !important;
|
||||||
|
margin: 0.2em;
|
||||||
|
padding: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.hide-block > .block) {
|
||||||
|
padding: 0.5em 0.25em;
|
||||||
|
box-shadow: unset;
|
||||||
|
border-width: 0;
|
||||||
|
border-color: unset;
|
||||||
|
border-radius: unset;
|
||||||
|
background: var(--block-background-fill);
|
||||||
|
width: 100%;
|
||||||
|
line-height: var(--line-sm);
|
||||||
|
}
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--layout-gap);
|
gap: var(--layout-gap);
|
||||||
@@ -177,14 +220,6 @@
|
|||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation-wrapper {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:not(.edit) {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
@@ -195,6 +230,11 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.animation-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 100;
|
||||||
|
}
|
||||||
|
|
||||||
.handle-widget:hover {
|
.handle-widget:hover {
|
||||||
background-color: #add8e680;
|
background-color: #add8e680;
|
||||||
}
|
}
|
||||||
@@ -249,14 +289,6 @@
|
|||||||
color: var(--input-placeholder-color);
|
color: var(--input-placeholder-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-edit-outline > :global(.block) {
|
|
||||||
border-color: var(--color-pink-500);
|
|
||||||
border-width: 2px;
|
|
||||||
border-style: dashed !important;
|
|
||||||
margin: 0.2em;
|
|
||||||
padding: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-edit-outline {
|
.widget-edit-outline {
|
||||||
border: 2px dashed var(--color-blue-400);
|
border: 2px dashed var(--color-blue-400);
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||||
import { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||||
import { Checkbox } from "@gradio/form"
|
import { Checkbox, TextBox } from "@gradio/form"
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import layoutState from "$lib/stores/layoutState";
|
import layoutState from "$lib/stores/layoutState";
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
import { LGraph } from "@litegraph-ts/core";
|
import { LGraph } from "@litegraph-ts/core";
|
||||||
import LightboxModal from "./LightboxModal.svelte";
|
import LightboxModal from "./LightboxModal.svelte";
|
||||||
import ComfyQueue from "./ComfyQueue.svelte";
|
import ComfyQueue from "./ComfyQueue.svelte";
|
||||||
|
import ComfyProperties from "./ComfyProperties.svelte";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
|
|
||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
@@ -23,9 +25,11 @@
|
|||||||
let queue: ComfyQueue = undefined;
|
let queue: ComfyQueue = undefined;
|
||||||
let mainElem: HTMLDivElement;
|
let mainElem: HTMLDivElement;
|
||||||
let uiPane: ComfyUIPane = undefined;
|
let uiPane: ComfyUIPane = undefined;
|
||||||
|
let props: ComfyProperties = undefined;
|
||||||
let containerElem: HTMLDivElement;
|
let containerElem: HTMLDivElement;
|
||||||
let resizeTimeout: NodeJS.Timeout | null;
|
let resizeTimeout: NodeJS.Timeout | null;
|
||||||
let hasShownUIHelpToast: boolean = false;
|
let hasShownUIHelpToast: boolean = false;
|
||||||
|
let uiTheme: string = "anapnoe";
|
||||||
|
|
||||||
let debugLayout: boolean = false;
|
let debugLayout: boolean = false;
|
||||||
|
|
||||||
@@ -43,7 +47,8 @@
|
|||||||
|
|
||||||
function queuePrompt() {
|
function queuePrompt() {
|
||||||
console.log("Queuing!");
|
console.log("Queuing!");
|
||||||
app.queuePrompt(0, 1);
|
const workflow = $layoutState.attrs.defaultSubgraph;
|
||||||
|
app.queuePrompt(0, 1, workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
||||||
@@ -52,11 +57,11 @@
|
|||||||
$: if ($uiState.uiEditMode)
|
$: if ($uiState.uiEditMode)
|
||||||
$layoutState.currentSelection = []
|
$layoutState.currentSelection = []
|
||||||
|
|
||||||
let graphSize = null;
|
let graphSize = 0;
|
||||||
|
|
||||||
function toggleGraph() {
|
function toggleGraph() {
|
||||||
if (graphSize == 0) {
|
if (graphSize == 0) {
|
||||||
graphSize = 100;
|
graphSize = 50;
|
||||||
app.resizeCanvas();
|
app.resizeCanvas();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -64,15 +69,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sidebarSize = 20;
|
let propsSidebarSize = 0; //15;
|
||||||
|
|
||||||
function toggleSidebar() {
|
function toggleProps() {
|
||||||
if (sidebarSize == 0) {
|
if (propsSidebarSize == 0) {
|
||||||
sidebarSize = 20;
|
propsSidebarSize = 15;
|
||||||
app.resizeCanvas();
|
app.resizeCanvas();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sidebarSize = 0;
|
propsSidebarSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let queueSidebarSize = 15;
|
||||||
|
|
||||||
|
function toggleQueue() {
|
||||||
|
if (queueSidebarSize == 0) {
|
||||||
|
queueSidebarSize = 15;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queueSidebarSize = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +158,8 @@
|
|||||||
(window as any).app = app;
|
(window as any).app = app;
|
||||||
(window as any).appPane = uiPane;
|
(window as any).appPane = uiPane;
|
||||||
|
|
||||||
|
await import('../../scss/ux.scss');
|
||||||
|
|
||||||
refreshView();
|
refreshView();
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -149,10 +168,21 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
{#if uiTheme === "anapnoe"}
|
||||||
|
<link rel="stylesheet" href="/src/scss/ux.scss">
|
||||||
|
{/if}
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="dropzone" class="dropzone"></div>
|
<div id="dropzone" class="dropzone"></div>
|
||||||
<div id="container" bind:this={containerElem}>
|
<div id="container" bind:this={containerElem}>
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||||
|
<Pane bind:size={propsSidebarSize}>
|
||||||
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
|
<ComfyProperties bind:this={props} />
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
<Pane>
|
<Pane>
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||||
<Pane>
|
<Pane>
|
||||||
@@ -165,7 +195,7 @@
|
|||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane bind:size={sidebarSize}>
|
<Pane bind:size={queueSidebarSize}>
|
||||||
<div class="sidebar-wrapper pane-wrapper">
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
<ComfyQueue bind:this={queue} />
|
<ComfyQueue bind:this={queue} />
|
||||||
</div>
|
</div>
|
||||||
@@ -174,13 +204,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="bottombar">
|
<div id="bottombar">
|
||||||
<Button variant="primary" on:click={queuePrompt}>
|
<Button variant="primary" on:click={queuePrompt}>
|
||||||
Run
|
Queue Prompt
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" on:click={toggleGraph}>
|
<Button variant="secondary" on:click={toggleGraph}>
|
||||||
Toggle Graph
|
Toggle Graph
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" on:click={toggleSidebar}>
|
<Button variant="secondary" on:click={toggleProps}>
|
||||||
Toggle Sidebar
|
Toggle Props
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" on:click={toggleQueue}>
|
||||||
|
Toggle Queue
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" on:click={doSave}>
|
<Button variant="secondary" on:click={doSave}>
|
||||||
Save
|
Save
|
||||||
@@ -197,14 +230,23 @@
|
|||||||
<Button variant="secondary" on:click={doRefreshCombos}>
|
<Button variant="secondary" on:click={doRefreshCombos}>
|
||||||
🔄
|
🔄
|
||||||
</Button>
|
</Button>
|
||||||
<Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||||
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/>
|
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/> -->
|
||||||
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
||||||
<label for="enable-ui-editing">Enable UI Editing</label>
|
<label class="label" for="enable-ui-editing">
|
||||||
|
<BlockTitle>Enable UI Editing</BlockTitle>
|
||||||
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="widgets">Widgets</option>
|
<option value="widgets">Widgets</option>
|
||||||
</select>
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="label" for="ui-theme">
|
||||||
|
<BlockTitle>Theme</BlockTitle>
|
||||||
|
<select id="ui-theme" name="ui-theme" bind:value={uiTheme}>
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="anapnoe">Anapnoe</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<LightboxModal />
|
<LightboxModal />
|
||||||
</div>
|
</div>
|
||||||
@@ -213,7 +255,7 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#container {
|
#container {
|
||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 70px);
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -293,7 +335,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:global(.splitpanes.comfy) {
|
:global(.splitpanes.comfy) {
|
||||||
max-height: calc(100vh - 60px);
|
max-height: calc(100vh - 70px);
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,4 +346,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.label > :global(span) {
|
||||||
|
top: 20%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import type TypedEmitter from "typed-emitter";
|
|||||||
import "@litegraph-ts/nodes-basic"
|
import "@litegraph-ts/nodes-basic"
|
||||||
import "@litegraph-ts/nodes-events"
|
import "@litegraph-ts/nodes-events"
|
||||||
import "@litegraph-ts/nodes-math"
|
import "@litegraph-ts/nodes-math"
|
||||||
|
import "@litegraph-ts/nodes-strings"
|
||||||
|
import "$lib/nodes/index"
|
||||||
import * as nodes from "$lib/nodes/index"
|
import * as nodes from "$lib/nodes/index"
|
||||||
|
|
||||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||||
@@ -24,10 +26,13 @@ import { toast } from '@zerodevx/svelte-toast'
|
|||||||
import ComfyGraph from "$lib/ComfyGraph";
|
import ComfyGraph from "$lib/ComfyGraph";
|
||||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import { promptToGraphVis } from "$lib/utils";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
LiteGraph.catch_exceptions = false;
|
LiteGraph.catch_exceptions = false;
|
||||||
|
LiteGraph.CANVAS_GRID_SIZE = 32;
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
// Load default visibility
|
// Load default visibility
|
||||||
@@ -52,9 +57,11 @@ export type SerializedPromptInputs = {
|
|||||||
class_type: string
|
class_type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedPromptOutput = Record<string, SerializedPromptInputs>
|
||||||
|
|
||||||
export type SerializedPrompt = {
|
export type SerializedPrompt = {
|
||||||
workflow: SerializedLGraph,
|
workflow: SerializedLGraph,
|
||||||
output: Record<string, SerializedPromptInputs>
|
output: SerializedPromptOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Progress = {
|
export type Progress = {
|
||||||
@@ -318,12 +325,12 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Distinguish frontend/backend connections
|
// Distinguish frontend/backend connections
|
||||||
const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
|
const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
|
||||||
for (const type of BACKEND_TYPES) {
|
for (const type of BACKEND_TYPES) {
|
||||||
setColor(type, "orange")
|
setColor(type, "orange")
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor("OUTPUT", "rebeccapurple")
|
setColor("IMAGE", "rebeccapurple")
|
||||||
setColor(BuiltInSlotType.EVENT, "lightseagreen")
|
setColor(BuiltInSlotType.EVENT, "lightseagreen")
|
||||||
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
||||||
}
|
}
|
||||||
@@ -418,7 +425,7 @@ export default class ComfyApp {
|
|||||||
* Converts the current graph workflow for sending to the API
|
* Converts the current graph workflow for sending to the API
|
||||||
* @returns The workflow and node links
|
* @returns The workflow and node links
|
||||||
*/
|
*/
|
||||||
async graphToPrompt(): Promise<SerializedPrompt> {
|
async graphToPrompt(tag: string | null = null): Promise<SerializedPrompt> {
|
||||||
// Run frontend-only logic
|
// Run frontend-only logic
|
||||||
this.lGraph.runStep(1)
|
this.lGraph.runStep(1)
|
||||||
|
|
||||||
@@ -436,6 +443,11 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
const node = node_ as ComfyBackendNode;
|
const node = node_ as ComfyBackendNode;
|
||||||
|
|
||||||
|
if (tag && node.tags.indexOf(tag) === -1) {
|
||||||
|
console.debug("Skipping tagged node", tag, node.tags)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (node.mode === NodeMode.NEVER) {
|
if (node.mode === NodeMode.NEVER) {
|
||||||
// Don't serialize muted nodes
|
// Don't serialize muted nodes
|
||||||
continue;
|
continue;
|
||||||
@@ -449,6 +461,11 @@ export default class ComfyApp {
|
|||||||
const inp = node.inputs[i];
|
const inp = node.inputs[i];
|
||||||
const inputLink = node.getInputLink(i)
|
const inputLink = node.getInputLink(i)
|
||||||
const inputNode = node.getInputNode(i)
|
const inputNode = node.getInputNode(i)
|
||||||
|
|
||||||
|
if (inputNode && tag && "tags" in inputNode && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!inputLink || !inputNode) {
|
if (!inputLink || !inputNode) {
|
||||||
if ("config" in inp) {
|
if ("config" in inp) {
|
||||||
const defaultValue = (inp as IComfyInputSlot).config?.defaultValue
|
const defaultValue = (inp as IComfyInputSlot).config?.defaultValue
|
||||||
@@ -487,17 +504,36 @@ export default class ComfyApp {
|
|||||||
if (parent) {
|
if (parent) {
|
||||||
const seen = {}
|
const seen = {}
|
||||||
let link = node.getInputLink(i);
|
let link = node.getInputLink(i);
|
||||||
while (parent && !parent.isBackendNode) {
|
|
||||||
|
const isValidParent = (parent: ComfyGraphNode) => {
|
||||||
|
if (!parent || parent.isBackendNode)
|
||||||
|
return false;
|
||||||
|
if ("tags" in parent && (parent.tags as string[]).indexOf(tag) === -1)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isValidParent(parent)) {
|
||||||
link = parent.getInputLink(link.origin_slot);
|
link = parent.getInputLink(link.origin_slot);
|
||||||
if (link && !seen[link.id]) {
|
if (link && !seen[link.id]) {
|
||||||
seen[link.id] = true
|
seen[link.id] = true
|
||||||
parent = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
const inputNode = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
||||||
|
if (inputNode && "tags" in inputNode && tag && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||||
|
console.debug("Skipping tagged parent node", tag, node.tags)
|
||||||
|
parent = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parent = inputNode;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
parent = null;
|
parent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link && parent && parent.isBackendNode) {
|
if (link && parent && parent.isBackendNode) {
|
||||||
|
if ("tags" in parent && tag && (parent.tags as string[]).indexOf(tag) === -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
const input = node.inputs[i]
|
const input = node.inputs[i]
|
||||||
// TODO can null be a legitimate value in some cases?
|
// TODO can null be a legitimate value in some cases?
|
||||||
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
||||||
@@ -520,15 +556,19 @@ export default class ComfyApp {
|
|||||||
if (Array.isArray(output[o].inputs[i])
|
if (Array.isArray(output[o].inputs[i])
|
||||||
&& output[o].inputs[i].length === 2
|
&& output[o].inputs[i].length === 2
|
||||||
&& !output[output[o].inputs[i][0]]) {
|
&& !output[output[o].inputs[i][0]]) {
|
||||||
|
console.debug("Prune removed node link", o, i, output[o].inputs[i])
|
||||||
delete output[o].inputs[i];
|
delete output[o].inputs[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn({ workflow, output })
|
||||||
|
console.warn(promptToGraphVis({ workflow, output }))
|
||||||
|
|
||||||
return { workflow, output };
|
return { workflow, output };
|
||||||
}
|
}
|
||||||
|
|
||||||
async queuePrompt(num: number, batchCount: number = 1) {
|
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||||
this.queueItems.push({ num, batchCount });
|
this.queueItems.push({ num, batchCount });
|
||||||
|
|
||||||
// Only have one action process the items so each one gets a unique seed correctly
|
// Only have one action process the items so each one gets a unique seed correctly
|
||||||
@@ -536,14 +576,23 @@ export default class ComfyApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tag === "")
|
||||||
|
tag = null;
|
||||||
|
|
||||||
this.processingQueue = true;
|
this.processingQueue = true;
|
||||||
try {
|
try {
|
||||||
while (this.queueItems.length) {
|
while (this.queueItems.length) {
|
||||||
({ num, batchCount } = this.queueItems.pop());
|
({ num, batchCount } = this.queueItems.pop());
|
||||||
console.log(`Queue get! ${num} ${batchCount}`);
|
console.debug(`Queue get! ${num} ${batchCount} ${tag}`);
|
||||||
|
|
||||||
for (let i = 0; i < batchCount; i++) {
|
for (let i = 0; i < batchCount; i++) {
|
||||||
const p = await this.graphToPrompt();
|
for (const node of this.lGraph._nodes_in_order) {
|
||||||
|
if ("beforeQueued" in node) {
|
||||||
|
(node as ComfyGraphNode).beforeQueued();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = await this.graphToPrompt(tag);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.queuePrompt(num, p);
|
await this.api.queuePrompt(num, p);
|
||||||
@@ -624,12 +673,8 @@ export default class ComfyApp {
|
|||||||
if ("config" in input) {
|
if ("config" in input) {
|
||||||
const comfyInput = input as IComfyInputSlot;
|
const comfyInput = input as IComfyInputSlot;
|
||||||
|
|
||||||
console.warn("RefreshCombo", comfyInput.defaultWidgetNode, comfyInput)
|
|
||||||
|
|
||||||
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) {
|
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) {
|
||||||
comfyInput.config.values = def["input"]["required"][comfyInput.name][0];
|
comfyInput.config.values = def["input"]["required"][comfyInput.name][0];
|
||||||
|
|
||||||
console.warn("RefreshCombo", comfyInput.config.values, def["input"]["required"][comfyInput.name])
|
|
||||||
const inputNode = node.getInputNode(index)
|
const inputNode = node.getInputNode(index)
|
||||||
|
|
||||||
if (inputNode && "doAutoConfig" in inputNode) {
|
if (inputNode && "doAutoConfig" in inputNode) {
|
||||||
|
|||||||
56
src/lib/components/ComfyComboProperty.svelte
Normal file
56
src/lib/components/ComfyComboProperty.svelte
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let value: string = "";
|
||||||
|
export let values: string[] = [""];
|
||||||
|
export let name: string = "";
|
||||||
|
let value_: string = ""
|
||||||
|
|
||||||
|
$: value;
|
||||||
|
$: handleChange(value);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
change: string;
|
||||||
|
submit: undefined;
|
||||||
|
blur: undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function handleChange(val: string) {
|
||||||
|
console.debug("combo handleChange", val, value_)
|
||||||
|
if (val != value_)
|
||||||
|
dispatch("change", val);
|
||||||
|
value_ = val
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="select-wrapper">
|
||||||
|
<BlockTitle>{name}</BlockTitle>
|
||||||
|
<div class="select">
|
||||||
|
<select on:blur bind:value>
|
||||||
|
{#each values as value}
|
||||||
|
<option {value}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.select-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-title {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let value: number = 0;
|
||||||
|
export let step: number = 1;
|
||||||
|
export let name: string = "";
|
||||||
|
let value_: number = 0;
|
||||||
|
|
||||||
|
$: value;
|
||||||
|
$: handleChange(value);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
change: number;
|
||||||
|
submit: undefined;
|
||||||
|
blur: undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function handleChange(val: number) {
|
||||||
|
if (val != value_)
|
||||||
|
dispatch("change", val);
|
||||||
|
value_ = val
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="number-wrapper">
|
||||||
|
<BlockTitle>{name}</BlockTitle>
|
||||||
|
<div class="number">
|
||||||
|
<input type="number" bind:value {step}>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.number-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
310
src/lib/components/ComfyProperties.svelte
Normal file
310
src/lib/components/ComfyProperties.svelte
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
import { TextBox, Checkbox } from "@gradio/form";
|
||||||
|
import { LGraphNode } from "@litegraph-ts/core"
|
||||||
|
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||||
|
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
||||||
|
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
||||||
|
|
||||||
|
let target: IDragItem | null = null;
|
||||||
|
let node: LGraphNode | null = null;
|
||||||
|
|
||||||
|
$: if ($layoutState.currentSelection.length > 0) {
|
||||||
|
const targetId = $layoutState.currentSelection.slice(-1)[0]
|
||||||
|
target = $layoutState.allItems[targetId].dragItem
|
||||||
|
if (target.type === "widget") {
|
||||||
|
node = (target as WidgetLayout).node
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($layoutState.currentSelectionNodes.length > 0) {
|
||||||
|
target = null;
|
||||||
|
node = $layoutState.currentSelectionNodes[0]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target = null
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetType: string = "???"
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (node != null)
|
||||||
|
targetType = node.type || "Node"
|
||||||
|
else if (target)
|
||||||
|
targetType = "Group"
|
||||||
|
else
|
||||||
|
targetType = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function validNodeProperty(spec: AttributesSpec, node: LGraphNode): boolean {
|
||||||
|
if (spec.validNodeTypes) {
|
||||||
|
return spec.validNodeTypes.indexOf(node.type) !== -1;
|
||||||
|
}
|
||||||
|
return spec.name in node.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAttribute(entry: AttributesSpec, target: IDragItem, value: any) {
|
||||||
|
if (target) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateAttribute", name, value)
|
||||||
|
|
||||||
|
target.attrs[name] = value
|
||||||
|
target.attrsChanged.set(!get(target.attrsChanged))
|
||||||
|
|
||||||
|
if (node && "propsChanged" in node) {
|
||||||
|
const comfyNode = node as ComfyWidgetNode
|
||||||
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProperty(entry: AttributesSpec, value: any) {
|
||||||
|
if (node) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateProperty", name, value)
|
||||||
|
|
||||||
|
node.properties[name] = value;
|
||||||
|
|
||||||
|
if ("propsChanged" in node) {
|
||||||
|
const comfyNode = node as ComfyWidgetNode
|
||||||
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVar(node: LGraphNode, entry: AttributesSpec) {
|
||||||
|
let value = node[entry.name]
|
||||||
|
if (entry.serialize)
|
||||||
|
value = entry.serialize(value)
|
||||||
|
console.debug("[ComfyProperties] getVar", entry, value, node)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVar(entry: any, value: any) {
|
||||||
|
if (node) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateProperty", name, value)
|
||||||
|
|
||||||
|
if (entry.deserialize)
|
||||||
|
value = entry.deserialize(value)
|
||||||
|
|
||||||
|
console.debug("[ComfyProperties] updateVar", entry, value, name, node)
|
||||||
|
node[name] = value;
|
||||||
|
|
||||||
|
if ("propsChanged" in node) {
|
||||||
|
const comfyNode = node as ComfyWidgetNode
|
||||||
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWorkflowAttribute(entry: AttributesSpec, value: any) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateWorkflowAttribute", name, value)
|
||||||
|
|
||||||
|
$layoutState.attrs[name] = value
|
||||||
|
$layoutState = $layoutState
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="props">
|
||||||
|
<div class="top">
|
||||||
|
<div class="target-name">
|
||||||
|
<span>
|
||||||
|
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}<span>
|
||||||
|
{#if targetType !== ""}
|
||||||
|
<span class="type">({targetType})</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="props-entries">
|
||||||
|
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
||||||
|
<div class="category-name">
|
||||||
|
<span>
|
||||||
|
<span class="title">{category.categoryName}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#each category.specs as spec(spec.name)}
|
||||||
|
{#if spec.location === "widget" && target && spec.name in target.attrs}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||||
|
on:input={(e) => updateAttribute(spec, target, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
values={spec.values}
|
||||||
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if node}
|
||||||
|
{#if spec.location === "nodeProps" && validNodeProperty(spec, node)}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
on:input={(e) => updateProperty(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
label={spec.name}
|
||||||
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
values={spec.values}
|
||||||
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if spec.location === "nodeVars" && spec.name in node}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
|
on:input={(e) => updateVar(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
values={spec.values}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else if spec.location === "workflow" && spec.name in $layoutState.attrs}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
values={spec.values}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.props-entry {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-name {
|
||||||
|
border-color: var(--neutral-400);
|
||||||
|
background: var(--neutral-300);
|
||||||
|
padding: 0.8rem 1.0rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
padding: 0.4rem 1.0rem;
|
||||||
|
border-color: var(--neutral-300);
|
||||||
|
background: var(--neutral-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-name, .category-name {
|
||||||
|
border-width: var(--block-border-width);
|
||||||
|
|
||||||
|
.type {
|
||||||
|
color: var(--neutral-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
/* width: 100%;
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0.5em; */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -57,18 +57,18 @@
|
|||||||
|
|
||||||
$: if (entries) {
|
$: if (entries) {
|
||||||
_entries = []
|
_entries = []
|
||||||
for (const entry of entries) {
|
// for (const entry of entries) {
|
||||||
for (const outputs of Object.values(entry.outputs)) {
|
// for (const outputs of Object.values(entry.outputs)) {
|
||||||
const allImages = outputs.images.map(r => {
|
// const allImages = outputs.images.map(r => {
|
||||||
// TODO configure backend URL
|
// // TODO configure backend URL
|
||||||
const url = "http://localhost:8188/view?"
|
// const url = "http://localhost:8188/view?"
|
||||||
const params = new URLSearchParams(r)
|
// const params = new URLSearchParams(r)
|
||||||
return url + params
|
// return url + params
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
_entries.push({ allImages, name: "Output" })
|
// _entries.push({ allImages, name: "Output" })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,15 @@
|
|||||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import BlockContainer from "./BlockContainer.svelte"
|
import BlockContainer from "./BlockContainer.svelte"
|
||||||
|
import { type Writable } from "svelte/store"
|
||||||
|
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||||
|
|
||||||
export let dragItem: IDragItem | null = null;
|
export let dragItem: IDragItem | null = null;
|
||||||
export let zIndex: number = 0;
|
export let zIndex: number = 0;
|
||||||
export let classes: string[] = [];
|
export let classes: string[] = [];
|
||||||
let container: ContainerLayout | null = null;
|
let container: ContainerLayout | null = null;
|
||||||
|
let attrsChanged: Writable<boolean> | null = null;
|
||||||
|
let propsChanged: Writable<number> | null = null;
|
||||||
let widget: WidgetLayout | null = null;
|
let widget: WidgetLayout | null = null;
|
||||||
let showHandles: boolean = false;
|
let showHandles: boolean = false;
|
||||||
|
|
||||||
@@ -17,14 +21,23 @@
|
|||||||
dragItem = null;
|
dragItem = null;
|
||||||
container = null;
|
container = null;
|
||||||
widget = null;
|
widget = null;
|
||||||
|
attrsChanged = null;
|
||||||
|
propsChanged = null;
|
||||||
}
|
}
|
||||||
else if (dragItem.type === "container") {
|
else if (dragItem.type === "container") {
|
||||||
container = dragItem as ContainerLayout;
|
container = dragItem as ContainerLayout;
|
||||||
|
attrsChanged = container.attrsChanged;
|
||||||
widget = null;
|
widget = null;
|
||||||
|
propsChanged = null;
|
||||||
}
|
}
|
||||||
else if (dragItem.type === "widget") {
|
else if (dragItem.type === "widget") {
|
||||||
widget = dragItem as WidgetLayout;
|
widget = dragItem as WidgetLayout;
|
||||||
|
attrsChanged = widget.attrsChanged;
|
||||||
container = null;
|
container = null;
|
||||||
|
if (widget.node && "propsChanged" in widget.node)
|
||||||
|
propsChanged = (widget.node as ComfyWidgetNode).propsChanged
|
||||||
|
else
|
||||||
|
propsChanged = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: showHandles = $uiState.uiEditMode === "widgets" // TODO
|
$: showHandles = $uiState.uiEditMode === "widgets" // TODO
|
||||||
@@ -35,21 +48,38 @@
|
|||||||
$: if ($queueState && widget && widget.node) {
|
$: if ($queueState && widget && widget.node) {
|
||||||
dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
|
dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWidgetClass() {
|
||||||
|
const title = widget.node.type.replace("/", "-").replace(".", "-")
|
||||||
|
return `widget--${title}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{#if container}
|
{#if container}
|
||||||
|
{#key $attrsChanged}
|
||||||
<BlockContainer {container} {classes} {zIndex} {showHandles} />
|
<BlockContainer {container} {classes} {zIndex} {showHandles} />
|
||||||
|
{/key}
|
||||||
{:else if widget && widget.node}
|
{:else if widget && widget.node}
|
||||||
<div class="widget" class:widget-edit-outline={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
{@const edit = $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
|
{#key $attrsChanged}
|
||||||
|
{#key $propsChanged}
|
||||||
|
<div class="widget {widget.attrs.classes} {getWidgetClass()}"
|
||||||
|
class:edit={edit}
|
||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
||||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
||||||
|
class:hidden={widget.attrs.hidden}
|
||||||
>
|
>
|
||||||
<svelte:component this={widget.node.svelteComponentType} {widget} />
|
<svelte:component this={widget.node.svelteComponentType} {widget} />
|
||||||
</div>
|
</div>
|
||||||
|
{#if widget.attrs.hidden && edit}
|
||||||
|
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||||
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -66,6 +96,10 @@
|
|||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden:not(.edit) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
@@ -76,6 +110,10 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handle-hidden {
|
||||||
|
background-color: #40404080;
|
||||||
|
}
|
||||||
|
|
||||||
.handle-widget:hover {
|
.handle-widget:hover {
|
||||||
background-color: #add8e680;
|
background-color: #add8e680;
|
||||||
}
|
}
|
||||||
@@ -85,7 +123,7 @@
|
|||||||
color: var(--neutral-400);
|
color: var(--neutral-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-edit-outline {
|
.edit {
|
||||||
border: 2px dashed var(--color-blue-400);
|
border: 2px dashed var(--color-blue-400);
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,18 +2,21 @@ import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnec
|
|||||||
import ComfyGraphNode from "./ComfyGraphNode";
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||||
|
import { toast } from '@zerodevx/svelte-toast'
|
||||||
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
|
||||||
export interface ComfyAfterQueuedAction extends Record<any, any> {
|
export interface ComfyQueueEventsProperties extends Record<any, any> {
|
||||||
prompt: SerializedPrompt
|
prompt: SerializedPrompt | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyAfterQueuedAction extends ComfyGraphNode {
|
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
override properties: ComfyCopyActionProperties = {
|
override properties: ComfyQueueEventsProperties = {
|
||||||
prompt: null
|
prompt: null
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
outputs: [
|
outputs: [
|
||||||
|
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
||||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
||||||
{ name: "prompt", type: "*" }
|
{ name: "prompt", type: "*" }
|
||||||
],
|
],
|
||||||
@@ -21,17 +24,22 @@ export class ComfyAfterQueuedAction extends ComfyGraphNode {
|
|||||||
|
|
||||||
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
||||||
if (property === "value") {
|
if (property === "value") {
|
||||||
this.setOutputData(0, this.properties.prompt)
|
this.setOutputData(2, this.properties.prompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExecute() {
|
override onExecute() {
|
||||||
this.setOutputData(0, this.properties.prompt)
|
this.setOutputData(2, this.properties.prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override beforeQueued() {
|
||||||
|
this.setProperty("value", null)
|
||||||
|
this.triggerSlot(0, "bang")
|
||||||
}
|
}
|
||||||
|
|
||||||
override afterQueued(p: SerializedPrompt) {
|
override afterQueued(p: SerializedPrompt) {
|
||||||
this.setProperty("value", p)
|
this.setProperty("value", p)
|
||||||
this.triggerSlot(0, "bang")
|
this.triggerSlot(1, "bang")
|
||||||
}
|
}
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
@@ -41,10 +49,52 @@ export class ComfyAfterQueuedAction extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
LiteGraph.registerNodeType({
|
||||||
class: ComfyAfterQueuedAction,
|
class: ComfyQueueEvents,
|
||||||
title: "Comfy.AfterQueuedAction",
|
title: "Comfy.QueueEvents",
|
||||||
desc: "Triggers a 'bang' event when a prompt is queued.",
|
desc: "Triggers a 'bang' event when a prompt is queued.",
|
||||||
type: "actions/after_queued"
|
type: "actions/queue_events"
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
||||||
|
images: GalleryOutput | null,
|
||||||
|
filename: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
||||||
|
override properties: ComfyOnExecutedEventProperties = {
|
||||||
|
images: null,
|
||||||
|
filename: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "images", type: "IMAGE" }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "images", type: "OUTPUT" },
|
||||||
|
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
if (this.properties.images !== null)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
|
}
|
||||||
|
|
||||||
|
override receiveOutput(output: any) {
|
||||||
|
if (output && "images" in output) {
|
||||||
|
this.setProperty("images", output as GalleryOutput)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
|
this.triggerSlot(1, "bang")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyOnExecutedEvent,
|
||||||
|
title: "Comfy.OnExecutedEvent",
|
||||||
|
desc: "Triggers a 'bang' event when a prompt output is received.",
|
||||||
|
type: "actions/on_executed"
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface ComfyCopyActionProperties extends Record<any, any> {
|
export interface ComfyCopyActionProperties extends Record<any, any> {
|
||||||
@@ -130,3 +180,74 @@ LiteGraph.registerNodeType({
|
|||||||
desc: "Swaps two inputs when triggered",
|
desc: "Swaps two inputs when triggered",
|
||||||
type: "actions/swap"
|
type: "actions/swap"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export interface ComfyNotifyActionProperties extends Record<any, any> {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyNotifyAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyNotifyActionProperties = {
|
||||||
|
message: "Nya."
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "message", type: "string" },
|
||||||
|
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const message = this.getInputData(0);
|
||||||
|
if (message) {
|
||||||
|
toast.push(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyNotifyAction,
|
||||||
|
title: "Comfy.NotifyAction",
|
||||||
|
desc: "Displays a message.",
|
||||||
|
type: "actions/notify"
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ComfyExecuteSubgraphActionProperties extends Record<any, any> {
|
||||||
|
tag: string | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyExecuteSubgraphActionProperties = {
|
||||||
|
tag: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "execute", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "tag", type: "string" }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
const tag = this.getInputData(1)
|
||||||
|
if (tag)
|
||||||
|
this.setProperty("tag", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const tag = this.getInputData(1) || this.properties.tag;
|
||||||
|
|
||||||
|
const app = (window as any)?.app;
|
||||||
|
if (!app)
|
||||||
|
return;
|
||||||
|
|
||||||
|
app.queuePrompt(0, 1, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyExecuteSubgraphAction,
|
||||||
|
title: "Comfy.ExecuteSubgraphAction",
|
||||||
|
desc: "Runs a part of the graph based on a tag",
|
||||||
|
type: "actions/execute_subgraph"
|
||||||
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
|||||||
import ComfyGraphNode from "./ComfyGraphNode";
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
import ComfyWidgets from "$lib/widgets"
|
import ComfyWidgets from "$lib/widgets"
|
||||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||||
|
import type { SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Base class for any node with configuration sent by the backend.
|
* Base class for any node with configuration sent by the backend.
|
||||||
@@ -25,10 +26,16 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
|||||||
// It just returns a hash like { "ui": { "images": results } } internally.
|
// It just returns a hash like { "ui": { "images": results } } internally.
|
||||||
// So this will need to be hardcoded for now.
|
// So this will need to be hardcoded for now.
|
||||||
if (["PreviewImage", "SaveImage"].indexOf(comfyClass) !== -1) {
|
if (["PreviewImage", "SaveImage"].indexOf(comfyClass) !== -1) {
|
||||||
this.addOutput("output", "OUTPUT");
|
this.addOutput("output", "IMAGE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tags this node belongs to
|
||||||
|
* Allows you to run subsections of the graph
|
||||||
|
*/
|
||||||
|
tags: string[] = []
|
||||||
|
|
||||||
private setup(nodeData: any) {
|
private setup(nodeData: any) {
|
||||||
var inputs = nodeData["input"]["required"];
|
var inputs = nodeData["input"]["required"];
|
||||||
if (nodeData["input"]["optional"] != undefined) {
|
if (nodeData["input"]["optional"] != undefined) {
|
||||||
@@ -74,16 +81,27 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
|||||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
// app.#invokeExtensionsAsync("nodeCreated", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
|
super.onSerialize(o);
|
||||||
|
(o as any).tags = this.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
override onConfigure(o: SerializedLGraphNode) {
|
||||||
|
super.onConfigure(o);
|
||||||
|
this.tags = (o as any).tags || []
|
||||||
|
}
|
||||||
|
|
||||||
override onExecuted(outputData: any) {
|
override onExecuted(outputData: any) {
|
||||||
console.warn("onExecuted outputs", outputData)
|
console.warn("onExecuted outputs", outputData)
|
||||||
for (let index = 0; index < this.outputs.length; index++) {
|
for (let index = 0; index < this.outputs.length; index++) {
|
||||||
const output = this.outputs[index]
|
const output = this.outputs[index]
|
||||||
if (output.type === "OUTPUT") {
|
if (output.type === "IMAGE") {
|
||||||
this.setOutputData(index, outputData)
|
this.setOutputData(index, outputData)
|
||||||
for (const node of this.getOutputNodes(index)) {
|
for (const node of this.getOutputNodes(index)) {
|
||||||
|
console.warn(node)
|
||||||
if ("receiveOutput" in node) {
|
if ("receiveOutput" in node) {
|
||||||
const widgetNode = node as ComfyWidgetNode;
|
const widgetNode = node as ComfyGraphNode;
|
||||||
widgetNode.receiveOutput();
|
widgetNode.receiveOutput(outputData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
|
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
|
||||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||||
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
|
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
|
||||||
import { LGraph, LGraphNode, LiteGraph, type SerializedLGraphNode } from "@litegraph-ts/core";
|
import { LGraph, LGraphNode, LiteGraph, type SerializedLGraphNode, type Vector2 } from "@litegraph-ts/core";
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||||
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
export type DefaultWidgetSpec = {
|
export type DefaultWidgetSpec = {
|
||||||
defaultWidgetNode: new (name?: string) => ComfyWidgetNode,
|
defaultWidgetNode: new (name?: string) => ComfyWidgetNode,
|
||||||
@@ -18,11 +20,28 @@ export type DefaultWidgetLayout = {
|
|||||||
export default class ComfyGraphNode extends LGraphNode {
|
export default class ComfyGraphNode extends LGraphNode {
|
||||||
isBackendNode?: boolean;
|
isBackendNode?: boolean;
|
||||||
|
|
||||||
|
beforeQueued?(): void;
|
||||||
afterQueued?(prompt: SerializedPrompt): void;
|
afterQueued?(prompt: SerializedPrompt): void;
|
||||||
onExecuted?(output: any): void;
|
onExecuted?(output: any): void;
|
||||||
|
|
||||||
defaultWidgets?: DefaultWidgetLayout
|
defaultWidgets?: DefaultWidgetLayout
|
||||||
|
|
||||||
|
/** Called when a backend node sends a ComfyUI output over a link */
|
||||||
|
receiveOutput(output: any) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override onResize(size: Vector2) {
|
||||||
|
if ((window as any)?.app?.shiftDown) {
|
||||||
|
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[0] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[1] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
|
this.size[0] = w;
|
||||||
|
this.size[1] = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (super.onResize)
|
||||||
|
super.onResize(size)
|
||||||
|
}
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
for (let index = 0; index < this.inputs.length; index++) {
|
for (let index = 0; index < this.inputs.length; index++) {
|
||||||
const input = this.inputs[index]
|
const input = this.inputs[index]
|
||||||
@@ -43,7 +62,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
for (let index = 0; index < this.inputs.length; index++) {
|
for (let index = 0; index < this.inputs.length; index++) {
|
||||||
const input = this.inputs[index]
|
const input = this.inputs[index]
|
||||||
const serInput = o.inputs[index]
|
const serInput = o.inputs[index]
|
||||||
if ("widgetNodeType" in serInput) {
|
if (serInput && "widgetNodeType" in serInput) {
|
||||||
const comfyInput = input as IComfyInputSlot
|
const comfyInput = input as IComfyInputSlot
|
||||||
const ty: string = serInput.widgetNodeType as any
|
const ty: string = serInput.widgetNodeType as any
|
||||||
const widgetNode = Object.values(LiteGraph.registered_node_types)
|
const widgetNode = Object.values(LiteGraph.registered_node_types)
|
||||||
|
|||||||
208
src/lib/nodes/ComfyImageCacheNode.ts
Normal file
208
src/lib/nodes/ComfyImageCacheNode.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
|
||||||
|
export interface ComfyImageCacheNodeProperties extends Record<any, any> {
|
||||||
|
images: GalleryOutput | null,
|
||||||
|
index: number,
|
||||||
|
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
|
||||||
|
genNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageCacheState = "none" | "uploading" | "failed" | "cached"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A node that can act as both an input and output image node by uploading
|
||||||
|
* the output file into ComfyUI's input folder.
|
||||||
|
*/
|
||||||
|
export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||||
|
override properties: ComfyImageCacheNodeProperties = {
|
||||||
|
images: null,
|
||||||
|
index: 0,
|
||||||
|
filenames: {},
|
||||||
|
genNumber: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "images", type: "OUTPUT" },
|
||||||
|
{ name: "index", type: "number" },
|
||||||
|
{ name: "store", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "clear", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "filename", type: "string" },
|
||||||
|
{ name: "state", type: "string" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private _uploadPromise: Promise<void> | null = null;
|
||||||
|
private _state: ImageCacheState = "none"
|
||||||
|
|
||||||
|
stateWidget: ITextWidget;
|
||||||
|
filenameWidget: ITextWidget;
|
||||||
|
|
||||||
|
constructor(name?: string) {
|
||||||
|
super(name)
|
||||||
|
this.stateWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"State",
|
||||||
|
"none"
|
||||||
|
);
|
||||||
|
this.stateWidget.disabled = true;
|
||||||
|
|
||||||
|
this.filenameWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"File",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
this.filenameWidget.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
||||||
|
if (property === "images") {
|
||||||
|
if (value != null)
|
||||||
|
this.properties.index = clamp(this.properties.index, 0, value.length)
|
||||||
|
else
|
||||||
|
this.properties.index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.properties.filenames && this.properties.images) {
|
||||||
|
const fileCount = this.properties.images.images.length;
|
||||||
|
const cachedCount = Object.keys(this.properties.filenames).length
|
||||||
|
console.warn(cachedCount, this.properties.filenames)
|
||||||
|
this.filenameWidget.value = `${fileCount} files, ${cachedCount} cached`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
const index = this.getInputData(1)
|
||||||
|
if (typeof index === "number")
|
||||||
|
this.setIndex(index)
|
||||||
|
|
||||||
|
const existing = this.properties.filenames[this.properties.index]
|
||||||
|
let state = "none"
|
||||||
|
if (existing)
|
||||||
|
state = existing.status
|
||||||
|
|
||||||
|
this.stateWidget.value = state
|
||||||
|
|
||||||
|
let filename = null
|
||||||
|
if (this.properties.index in this.properties.filenames)
|
||||||
|
filename = this.properties.filenames[this.properties.index].filename
|
||||||
|
|
||||||
|
this.setOutputData(0, filename)
|
||||||
|
this.setOutputData(1, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setIndex(newIndex: number, force: boolean = false) {
|
||||||
|
if (newIndex === this.properties.index && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!this.properties.images || newIndex < 0 || newIndex >= this.properties.images.images.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setProperty("index", newIndex)
|
||||||
|
|
||||||
|
const data = this.properties.images.images[newIndex]
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.properties.filenames ||= {}
|
||||||
|
const existing = this.properties.filenames[newIndex]
|
||||||
|
|
||||||
|
if (existing != null && existing.status === "cached") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastGenNumber = this.properties.genNumber
|
||||||
|
|
||||||
|
// ComfyUI's LoadImage node only operates on files in its input
|
||||||
|
// folder. Usually we're dealing with an image in either the output
|
||||||
|
// folder (SaveImage) or the temp folder (PreviewImage). So we have
|
||||||
|
// to copy the image into ComfyUI's input folder first by using
|
||||||
|
// their upload API.
|
||||||
|
|
||||||
|
if (data.subfolder === "input") {
|
||||||
|
// Already in the correct folder for use by LoadImage
|
||||||
|
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
const url = "http://localhost:8188" // TODO make configurable
|
||||||
|
const params = new URLSearchParams(data)
|
||||||
|
|
||||||
|
const promise = fetch(url + "/view?" + params)
|
||||||
|
.then((r) => r.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
console.debug("Fetchin", url, params)
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", blob, data.filename);
|
||||||
|
return fetch(
|
||||||
|
new Request(url + "/upload/image", {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((json) => {
|
||||||
|
console.debug("Gottem", json)
|
||||||
|
if (lastGenNumber === this.properties.genNumber) {
|
||||||
|
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("[ComfyImageCacheNode] New generation since index switched!")
|
||||||
|
}
|
||||||
|
this._uploadPromise = null;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Error uploading:", e)
|
||||||
|
if (lastGenNumber === this.properties.genNumber) {
|
||||||
|
this.properties.filenames[newIndex] = { filename: null, status: "failed" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("[ComfyImageCacheNode] New generation since index switched!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this._uploadPromise)
|
||||||
|
this._uploadPromise.then(() => promise)
|
||||||
|
else
|
||||||
|
this._uploadPromise = promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any) {
|
||||||
|
if (action === "clear") {
|
||||||
|
this.setProperty("images", null)
|
||||||
|
this.setProperty("filenames", {})
|
||||||
|
this.setProperty("index", 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = this.getInputLink(0)
|
||||||
|
|
||||||
|
if (link.data && "images" in link.data) {
|
||||||
|
this.setProperty("genNumber", this.properties.genNumber + 1)
|
||||||
|
this.setProperty("images", link.data as GalleryOutput)
|
||||||
|
this.setProperty("filenames", {})
|
||||||
|
console.debug("[ComfyImageCacheNode] Received output!", link.data)
|
||||||
|
this.setIndex(0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyImageCacheNode,
|
||||||
|
title: "Comfy.ImageCache",
|
||||||
|
desc: "Allows reusing a previously output image by uploading it into ComfyUI's input folder.",
|
||||||
|
type: "image/cache"
|
||||||
|
})
|
||||||
@@ -94,9 +94,9 @@ export class ComfySelectorTwo extends ComfyGraphNode {
|
|||||||
ctx.fillStyle = "#AFB";
|
ctx.fillStyle = "#AFB";
|
||||||
var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6;
|
var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(50, y);
|
ctx.moveTo(65, y);
|
||||||
ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT);
|
ctx.lineTo(65, y + LiteGraph.NODE_SLOT_HEIGHT);
|
||||||
ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);
|
ctx.lineTo(49, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
override isBackendNode = false;
|
override isBackendNode = false;
|
||||||
override serialize_widgets = true;
|
override serialize_widgets = true;
|
||||||
|
|
||||||
outputIndex: number = 0;
|
outputIndex: number | null = 0;
|
||||||
inputIndex: number = 0;
|
inputIndex: number = 0;
|
||||||
changedIndex: number = 1;
|
changedIndex: number | null = 1;
|
||||||
|
|
||||||
displayWidget: ITextWidget;
|
displayWidget: ITextWidget;
|
||||||
|
|
||||||
@@ -91,10 +91,10 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
console.debug("[Widget] valueUpdated", this, value)
|
console.debug("[Widget] valueUpdated", this, value)
|
||||||
this.displayWidget.value = this.formatValue(value)
|
this.displayWidget.value = this.formatValue(value)
|
||||||
|
|
||||||
if (this.outputs.length >= this.outputIndex) {
|
if (this.outputIndex !== null && this.outputs.length >= this.outputIndex) {
|
||||||
this.setOutputData(this.outputIndex, get(this.value))
|
this.setOutputData(this.outputIndex, get(this.value))
|
||||||
}
|
}
|
||||||
if (this.outputs.length >= this.changedIndex) {
|
if (this.changedIndex !== null & this.outputs.length >= this.changedIndex) {
|
||||||
const changedOutput = this.outputs[this.changedIndex]
|
const changedOutput = this.outputs[this.changedIndex]
|
||||||
if (changedOutput.type === BuiltInSlotType.EVENT)
|
if (changedOutput.type === BuiltInSlotType.EVENT)
|
||||||
this.triggerSlot(this.changedIndex, "changed")
|
this.triggerSlot(this.changedIndex, "changed")
|
||||||
@@ -119,7 +119,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
if (this.inputs.length >= this.inputIndex) {
|
if (this.inputs.length >= this.inputIndex) {
|
||||||
const data = this.getInputData(this.inputIndex)
|
const data = this.getInputData(this.inputIndex)
|
||||||
if (data) { // TODO can "null" be a legitimate value here?
|
if (data) { // TODO can "null" be a legitimate value here?
|
||||||
console.log(data)
|
|
||||||
this.setValue(data)
|
this.setValue(data)
|
||||||
const input = this.getInputLink(this.inputIndex)
|
const input = this.getInputLink(this.inputIndex)
|
||||||
input.data = null;
|
input.data = null;
|
||||||
@@ -135,10 +134,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when a backend node sends a ComfyUI output over a link */
|
|
||||||
receiveOutput() {
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnectOutput(
|
onConnectOutput(
|
||||||
outputIndex: number,
|
outputIndex: number,
|
||||||
inputType: INodeInputSlot["type"],
|
inputType: INodeInputSlot["type"],
|
||||||
@@ -146,7 +141,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
inputNode: LGraphNode,
|
inputNode: LGraphNode,
|
||||||
inputIndex: number
|
inputIndex: number
|
||||||
): boolean {
|
): boolean {
|
||||||
if (this.autoConfig && "config" in input) {
|
if (this.autoConfig && "config" in input && this.outputs.length === 0) {
|
||||||
this.doAutoConfig(input as IComfyInputSlot)
|
this.doAutoConfig(input as IComfyInputSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,31 +397,57 @@ export type GalleryOutputEntry = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||||
|
index: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||||
override properties: ComfyGalleryProperties = {
|
override properties: ComfyGalleryProperties = {
|
||||||
defaultValue: []
|
defaultValue: [],
|
||||||
|
index: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
inputs: [
|
inputs: [
|
||||||
{ name: "images", type: "OUTPUT" }
|
{ name: "images", type: "OUTPUT" },
|
||||||
|
{ name: "store", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "clear", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "selected_index", type: "number" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
override svelteComponentType = GalleryWidget
|
override svelteComponentType = GalleryWidget
|
||||||
override copyFromInputLink = false;
|
override copyFromInputLink = false;
|
||||||
|
override outputIndex = null;
|
||||||
|
override changedIndex = null;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(name, [])
|
super(name, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
override afterQueued() {
|
override onExecute() {
|
||||||
let queue = get(queueState)
|
this.setOutputData(0, this.properties.index)
|
||||||
if (!(typeof queue.queueRemaining === "number" && queue.queueRemaining > 1)) {
|
}
|
||||||
|
|
||||||
|
override onAction(action: any) {
|
||||||
|
if (action === "clear") {
|
||||||
this.setValue([])
|
this.setValue([])
|
||||||
}
|
}
|
||||||
|
else if (action === "store") {
|
||||||
|
const link = this.getInputLink(0)
|
||||||
|
if (link.data && "images" in link.data) {
|
||||||
|
const data = link.data as GalleryOutput
|
||||||
|
console.debug("[ComfyGalleryNode] Received output!", data)
|
||||||
|
|
||||||
|
const galleryItems: GradioFileData[] = this.convertItems(link.data)
|
||||||
|
|
||||||
|
// const currentValue = get(this.value)
|
||||||
|
// this.setValue(currentValue.concat(galleryItems))
|
||||||
|
this.setValue(galleryItems)
|
||||||
|
}
|
||||||
|
this.setProperty("index", 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override formatValue(value: GradioFileData[] | null): string {
|
override formatValue(value: GradioFileData[] | null): string {
|
||||||
@@ -452,18 +473,10 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
else {
|
else {
|
||||||
super.setValue([])
|
super.setValue([])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
receiveOutput() {
|
const len = get(this.value).length
|
||||||
const link = this.getInputLink(0)
|
if (this.properties.index < 0 || this.properties.index >= len) {
|
||||||
if (link.data && "images" in link.data) {
|
this.setProperty("index", clamp(this.properties.index, 0, len))
|
||||||
const data = link.data as GalleryOutput
|
|
||||||
console.debug("[ComfyGalleryNode] Received output!", data)
|
|
||||||
|
|
||||||
const galleryItems: GradioFileData[] = this.convertItems(link.data)
|
|
||||||
|
|
||||||
const currentValue = get(this.value)
|
|
||||||
this.setValue(currentValue.concat(galleryItems))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export { default as ComfyReroute } from "./ComfyReroute"
|
export { default as ComfyReroute } from "./ComfyReroute"
|
||||||
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
||||||
export { ComfyCopyAction, ComfySwapAction } from "./ComfyActionNodes"
|
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
|
||||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||||
export { default as ComfySelector } from "./ComfySelector"
|
export { default as ComfySelector } from "./ComfySelector"
|
||||||
|
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
|
||||||
|
|||||||
@@ -11,20 +11,45 @@ type DragItemEntry = {
|
|||||||
parent: IDragItem | null
|
parent: IDragItem | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LayoutAttributes = {
|
||||||
|
defaultSubgraph: string
|
||||||
|
}
|
||||||
|
|
||||||
export type LayoutState = {
|
export type LayoutState = {
|
||||||
root: IDragItem | null,
|
root: IDragItem | null,
|
||||||
allItems: Record<DragItemID, DragItemEntry>,
|
allItems: Record<DragItemID, DragItemEntry>,
|
||||||
allItemsByNode: Record<number, DragItemEntry>,
|
allItemsByNode: Record<number, DragItemEntry>,
|
||||||
currentId: number,
|
currentId: number,
|
||||||
currentSelection: DragItemID[],
|
currentSelection: DragItemID[],
|
||||||
|
currentSelectionNodes: LGraphNode[],
|
||||||
isConfiguring: boolean,
|
isConfiguring: boolean,
|
||||||
isMenuOpen: boolean
|
isMenuOpen: boolean,
|
||||||
|
attrs: LayoutAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Attributes = {
|
||||||
|
direction: "horizontal" | "vertical",
|
||||||
|
title: string,
|
||||||
|
showTitle: boolean,
|
||||||
|
classes: string,
|
||||||
|
blockVariant?: "block" | "hidden",
|
||||||
|
hidden?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
|
flexGrow?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttributesSpec = {
|
export type AttributesSpec = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
type: string,
|
||||||
editable: boolean
|
location: "widget" | "nodeProps" | "nodeVars" | "workflow"
|
||||||
|
editable: boolean,
|
||||||
|
|
||||||
|
values?: string[],
|
||||||
|
hidden?: boolean,
|
||||||
|
validNodeTypes?: string[],
|
||||||
|
|
||||||
|
serialize?: (arg: any) => string,
|
||||||
|
deserialize?: (arg: string) => any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttributesCategorySpec = {
|
export type AttributesCategorySpec = {
|
||||||
@@ -41,40 +66,116 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
{
|
{
|
||||||
name: "title",
|
name: "title",
|
||||||
type: "string",
|
type: "string",
|
||||||
|
location: "widget",
|
||||||
editable: true,
|
editable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "showTitle",
|
name: "hidden",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
editable: true,
|
location: "widget",
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled",
|
||||||
|
type: "boolean",
|
||||||
|
location: "widget",
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "direction",
|
name: "direction",
|
||||||
type: "string",
|
type: "enum",
|
||||||
|
location: "widget",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
values: ["horizontal", "vertical"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "flexGrow",
|
||||||
|
type: "number",
|
||||||
|
location: "widget",
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "classes",
|
name: "classes",
|
||||||
type: "string",
|
type: "string",
|
||||||
|
location: "widget",
|
||||||
editable: true,
|
editable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "blockVariant",
|
||||||
|
type: "enum",
|
||||||
|
location: "widget",
|
||||||
|
editable: true,
|
||||||
|
values: ["block", "hidden"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "behavior",
|
||||||
|
specs: [
|
||||||
|
// Node variables
|
||||||
|
{
|
||||||
|
name: "tags",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeVars",
|
||||||
|
editable: true,
|
||||||
|
serialize: (arg: string[]) => arg.join(","),
|
||||||
|
deserialize: (arg: string) => {
|
||||||
|
if (arg === "")
|
||||||
|
return []
|
||||||
|
return arg.split(",").map(s => s.trim())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Range
|
||||||
|
{
|
||||||
|
name: "min",
|
||||||
|
type: "number",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max",
|
||||||
|
type: "number",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "step",
|
||||||
|
type: "number",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Button
|
||||||
|
{
|
||||||
|
name: "message",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/button"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workflow
|
||||||
|
{
|
||||||
|
name: "defaultSubgraph",
|
||||||
|
type: "string",
|
||||||
|
location: "workflow",
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export { ALL_ATTRIBUTES };
|
export { ALL_ATTRIBUTES };
|
||||||
|
|
||||||
export type Attributes = {
|
|
||||||
direction: "horizontal" | "vertical",
|
|
||||||
title: string,
|
|
||||||
showTitle: boolean,
|
|
||||||
classes: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDragItem {
|
export interface IDragItem {
|
||||||
type: string,
|
type: string,
|
||||||
id: DragItemID,
|
id: DragItemID,
|
||||||
isNodeExecuting?: boolean,
|
isNodeExecuting?: boolean,
|
||||||
attrs: Attributes
|
attrs: Attributes,
|
||||||
|
attrsChanged: Writable<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerLayout extends IDragItem {
|
export interface ContainerLayout extends IDragItem {
|
||||||
@@ -112,8 +213,12 @@ const store: Writable<LayoutState> = writable({
|
|||||||
allItemsByNode: {},
|
allItemsByNode: {},
|
||||||
currentId: 0,
|
currentId: 0,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: true
|
isConfiguring: true,
|
||||||
|
attrs: {
|
||||||
|
defaultSubgraph: ""
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
||||||
@@ -141,11 +246,14 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial<Attributes>
|
|||||||
const dragItem: ContainerLayout = {
|
const dragItem: ContainerLayout = {
|
||||||
type: "container",
|
type: "container",
|
||||||
id: `${state.currentId++}`,
|
id: `${state.currentId++}`,
|
||||||
|
attrsChanged: writable(false),
|
||||||
attrs: {
|
attrs: {
|
||||||
title: "Container",
|
title: "Container",
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
classes: "",
|
classes: "",
|
||||||
|
blockVariant: "block",
|
||||||
|
flexGrow: 100,
|
||||||
...attrs
|
...attrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,11 +274,13 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia
|
|||||||
type: "widget",
|
type: "widget",
|
||||||
id: `${state.currentId++}`,
|
id: `${state.currentId++}`,
|
||||||
node: node,
|
node: node,
|
||||||
|
attrsChanged: writable(false),
|
||||||
attrs: {
|
attrs: {
|
||||||
title: widgetName,
|
title: widgetName,
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
classes: "",
|
classes: "",
|
||||||
|
flexGrow: 100,
|
||||||
...attrs
|
...attrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +420,9 @@ function groupItems(dragItems: IDragItem[], attrs: Partial<Attributes> = {}): Co
|
|||||||
index = indexFound
|
index = indexFound
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = addContainer(parent as ContainerLayout, attrs, index)
|
const title = dragItems.length <= 1 ? "" : "Group";
|
||||||
|
|
||||||
|
const container = addContainer(parent as ContainerLayout, { title, ...attrs }, index)
|
||||||
|
|
||||||
for (const item of dragItems) {
|
for (const item of dragItems) {
|
||||||
moveItem(item, container)
|
moveItem(item, container)
|
||||||
@@ -366,15 +478,17 @@ function initDefaultLayout() {
|
|||||||
store.set({
|
store.set({
|
||||||
root: null,
|
root: null,
|
||||||
allItems: {},
|
allItems: {},
|
||||||
|
allItemsByNode: {},
|
||||||
currentId: 0,
|
currentId: 0,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false
|
isConfiguring: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const root = addContainer(null, { direction: "horizontal", showTitle: false });
|
const root = addContainer(null, { direction: "horizontal", title: "" });
|
||||||
const left = addContainer(root, { direction: "vertical", showTitle: false });
|
const left = addContainer(root, { direction: "vertical", title: "" });
|
||||||
const right = addContainer(root, { direction: "vertical", showTitle: false });
|
const right = addContainer(root, { direction: "vertical", title: "" });
|
||||||
|
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
state.root = root;
|
state.root = root;
|
||||||
@@ -387,6 +501,7 @@ export type SerializedLayoutState = {
|
|||||||
root: DragItemID | null,
|
root: DragItemID | null,
|
||||||
allItems: Record<DragItemID, SerializedDragEntry>,
|
allItems: Record<DragItemID, SerializedDragEntry>,
|
||||||
currentId: number,
|
currentId: number,
|
||||||
|
attrs: LayoutAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedDragEntry = {
|
export type SerializedDragEntry = {
|
||||||
@@ -424,6 +539,7 @@ function serialize(): SerializedLayoutState {
|
|||||||
root: state.root?.id,
|
root: state.root?.id,
|
||||||
allItems,
|
allItems,
|
||||||
currentId: state.currentId,
|
currentId: state.currentId,
|
||||||
|
attrs: state.attrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +552,8 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
const dragItem: IDragItem = {
|
const dragItem: IDragItem = {
|
||||||
type: entry.dragItem.type,
|
type: entry.dragItem.type,
|
||||||
id: entry.dragItem.id,
|
id: entry.dragItem.id,
|
||||||
attrs: entry.dragItem.attrs
|
attrs: entry.dragItem.attrs,
|
||||||
|
attrsChanged: writable(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
const dragEntry: DragItemEntry = {
|
const dragEntry: DragItemEntry = {
|
||||||
@@ -476,8 +593,10 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
allItemsByNode,
|
allItemsByNode,
|
||||||
currentId: data.currentId,
|
currentId: data.currentId,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false
|
isConfiguring: false,
|
||||||
|
attrs: data.attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("[layoutState] deserialize", data, state)
|
console.debug("[layoutState] deserialize", data, state)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export type UIState = {
|
|||||||
nodesLocked: boolean,
|
nodesLocked: boolean,
|
||||||
graphLocked: boolean,
|
graphLocked: boolean,
|
||||||
autoAddUI: boolean,
|
autoAddUI: boolean,
|
||||||
uiEditMode: UIEditMode
|
uiEditMode: UIEditMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableUIStateStore = Writable<UIState>;
|
export type WritableUIStateStore = Writable<UIState>;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import ComfyApp from "./components/ComfyApp";
|
import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp";
|
||||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import layoutState from "$lib/stores/layoutState"
|
import layoutState from "$lib/stores/layoutState"
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
|
import type { SerializedLGraph } from "@litegraph-ts/core";
|
||||||
|
|
||||||
export function clamp(n: number, min: number, max: number): number {
|
export function clamp(n: number, min: number, max: number): number {
|
||||||
return Math.min(Math.max(n, min), max)
|
return Math.min(Math.max(n, min), max)
|
||||||
@@ -36,6 +37,8 @@ export function startDrag(evt: MouseEvent) {
|
|||||||
|
|
||||||
const item = ls.allItems[dragItemId].dragItem
|
const item = ls.allItems[dragItemId].dragItem
|
||||||
|
|
||||||
|
console.debug("startDrag", item)
|
||||||
|
|
||||||
if (evt.ctrlKey) {
|
if (evt.ctrlKey) {
|
||||||
const index = ls.currentSelection.indexOf(item.id)
|
const index = ls.currentSelection.indexOf(item.id)
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
@@ -47,8 +50,48 @@ export function startDrag(evt: MouseEvent) {
|
|||||||
else {
|
else {
|
||||||
ls.currentSelection = [item.id]
|
ls.currentSelection = [item.id]
|
||||||
}
|
}
|
||||||
|
ls.currentSelectionNodes = [];
|
||||||
|
|
||||||
layoutState.set(ls)
|
layoutState.set(ls)
|
||||||
};
|
};
|
||||||
|
|
||||||
export function stopDrag(evt: MouseEvent) {
|
export function stopDrag(evt: MouseEvent) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function workflowToGraphVis(workflow: SerializedLGraph): string {
|
||||||
|
let out = "digraph {\n"
|
||||||
|
|
||||||
|
for (const link of workflow.links) {
|
||||||
|
const nodeA = workflow.nodes.find(n => n.id === link[1])
|
||||||
|
const nodeB = workflow.nodes.find(n => n.id === link[3])
|
||||||
|
out += `"${link[1]}_${nodeA.title}" -> "${link[3]}_${nodeB.title}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
out += "}"
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||||
|
let out = "digraph {\n"
|
||||||
|
|
||||||
|
for (const pair of Object.entries(prompt.output)) {
|
||||||
|
const [id, o] = pair;
|
||||||
|
const outNode = prompt.workflow.nodes.find(n => n.id == id)
|
||||||
|
for (const pair2 of Object.entries(o.inputs)) {
|
||||||
|
const [inpName, i] = pair2;
|
||||||
|
|
||||||
|
if (Array.isArray(i) && i.length === 2 && typeof i[0] === "string" && typeof i[1] === "number") {
|
||||||
|
// Link
|
||||||
|
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
||||||
|
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Value
|
||||||
|
out += `"${id}-${inpName}-${typeof i}" -> "${outNode.title}"\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out += "}"
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,15 +28,19 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper gr-button">
|
<div class="wrapper gradio-button">
|
||||||
{#if node !== null}
|
{#if node !== null}
|
||||||
<Button on:click={onClick} variant="primary" {style}>
|
<Button
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
|
on:click={onClick}
|
||||||
|
variant="primary"
|
||||||
|
{style}>
|
||||||
{widget.attrs.title}
|
{widget.attrs.title}
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -63,16 +63,19 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper gr-combo" class:updated={werePropsChanged}>
|
<div class="wrapper comfy-combo" class:updated={werePropsChanged}>
|
||||||
{#key $propsChanged}
|
{#key $propsChanged}
|
||||||
{#if node !== null && nodeValue !== null}
|
{#if node !== null && nodeValue !== null}
|
||||||
<label>
|
<label>
|
||||||
|
{#if widget.attrs.title !== ""}
|
||||||
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
||||||
|
{/if}
|
||||||
<Select
|
<Select
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
bind:items={node.properties.values}
|
items={node.properties.values}
|
||||||
disabled={node.properties.values.length === 0}
|
disabled={widget.attrs.disabled || node.properties.values.length === 0}
|
||||||
clearable={false}
|
clearable={false}
|
||||||
|
showChevron={true}
|
||||||
on:change
|
on:change
|
||||||
on:select
|
on:select
|
||||||
on:filter
|
on:filter
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { ComfyGalleryNode } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyGalleryNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
|
import type { SelectData as GradioSelectData } from "@gradio/utils";
|
||||||
|
import { clamp } from "$lib/utils";
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
export let widget: WidgetLayout | null = null;
|
||||||
let node: ComfyGalleryNode | null = null;
|
let node: ComfyGalleryNode | null = null;
|
||||||
@@ -21,6 +23,11 @@
|
|||||||
node = widget.node as ComfyGalleryNode
|
node = widget.node as ComfyGalleryNode
|
||||||
nodeValue = node.value;
|
nodeValue = node.value;
|
||||||
propsChanged = node.propsChanged;
|
propsChanged = node.propsChanged;
|
||||||
|
|
||||||
|
const len = $nodeValue.length
|
||||||
|
if (node.properties.index < 0 || node.properties.index >= len) {
|
||||||
|
node.setProperty("index", clamp(node.properties.index, 0, len))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,7 +38,8 @@
|
|||||||
}
|
}
|
||||||
let element: HTMLDivElement;
|
let element: HTMLDivElement;
|
||||||
|
|
||||||
function updateForLightbox() {
|
function onSelect(e: CustomEvent<GradioSelectData>) {
|
||||||
|
// Setup lightbox
|
||||||
// Wait for gradio gallery to show the large preview image, if no timeout then
|
// Wait for gradio gallery to show the large preview image, if no timeout then
|
||||||
// the event might fire too early
|
// the event might fire too early
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -41,21 +49,24 @@
|
|||||||
}
|
}
|
||||||
ImageViewer.instance.updateOnBackgroundChange();
|
ImageViewer.instance.updateOnBackgroundChange();
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
|
// Update index
|
||||||
|
node.setProperty("index", e.detail.index as number)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div class="wrapper comfy-gallery-widget gr-gallery" bind:this={element}>
|
<div class="wrapper comfy-gallery-widget gradio-gallery" bind:this={element}>
|
||||||
{#if widget && node && nodeValue}
|
{#if widget && node && nodeValue}
|
||||||
<Block variant="solid" padding={false}>
|
<Block variant="solid" padding={false}>
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
<Gallery
|
<Gallery
|
||||||
bind:value={$nodeValue}
|
bind:value={$nodeValue}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
show_label={true}
|
show_label={widget.attrs.title !== ""}
|
||||||
{style}
|
{style}
|
||||||
root={""}
|
root={""}
|
||||||
root_url={""}
|
root_url={""}
|
||||||
on:select={updateForLightbox}
|
on:select={onSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
|
|||||||
@@ -37,12 +37,26 @@
|
|||||||
$nodeValue = option
|
$nodeValue = option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let gradient: string = ""
|
||||||
|
let elem: HTMLDivElement = null;
|
||||||
|
|
||||||
|
$: if (elem && node !== null && option !== null && (!$propsChanged || $propsChanged)) {
|
||||||
|
const slider = elem.querySelector("input[type='range']") as any
|
||||||
|
//const range_selectors = "[id$='_clone']:is(input[type='range'])";
|
||||||
|
let spacing = ((slider.step / ( slider.max - slider.min )) * 100.0);
|
||||||
|
let tsp = 'max(3px, calc('+spacing+'% - 2px))';
|
||||||
|
let fsp = 'max(4px, calc('+spacing+'% + 0px))';
|
||||||
|
const style = elem.style;
|
||||||
|
style.setProperty('--ae-slider-bg-overlay', 'repeating-linear-gradient( 90deg, transparent, transparent '+tsp+', var(--ae-input-border-color) '+tsp+', var(--ae-input-border-color) '+fsp+' )');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper gr-range">
|
<div class="wrapper gradio-slider" bind:this={elem}>
|
||||||
{#if node !== null && option !== null}
|
{#if node !== null && option !== null}
|
||||||
<Range
|
<Range
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
minimum={node.properties.min}
|
minimum={node.properties.min}
|
||||||
maximum={node.properties.max}
|
maximum={node.properties.max}
|
||||||
step={node.properties.step}
|
step={node.properties.step}
|
||||||
|
|||||||
@@ -27,11 +27,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper gr-textbox">
|
<div class="wrapper gradio-textbox">
|
||||||
{#if node !== null && nodeValue !== null}
|
{#if node !== null && nodeValue !== null}
|
||||||
<TextBox
|
<TextBox
|
||||||
bind:value={$nodeValue}
|
bind:value={$nodeValue}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
lines={node.properties.multiline ? 5 : 1}
|
lines={node.properties.multiline ? 5 : 1}
|
||||||
max_lines={node.properties.multiline ? 5 : 1}
|
max_lines={node.properties.multiline ? 5 : 1}
|
||||||
show_label={true}
|
show_label={true}
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
@import "gradio"
|
@import "gradio";
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|||||||
815
src/scss/ux.scss
Normal file
815
src/scss/ux.scss
Normal file
@@ -0,0 +1,815 @@
|
|||||||
|
/*
|
||||||
|
Theme Name: DarkUX
|
||||||
|
Author: anapnoe
|
||||||
|
Author URI: https://github.com/anapnoe/stable-diffusion-webui
|
||||||
|
Version: 1.0
|
||||||
|
License: GNU General Public License
|
||||||
|
*/
|
||||||
|
:root{
|
||||||
|
--ae-extra-networks-card-size: 1;
|
||||||
|
--ae-extra-networks-card-real-size: calc(var(--ae-extra-networks-card-size) * 14vh);
|
||||||
|
--ae-extra-networks-visible-rows: 2;
|
||||||
|
--ae-extra-networks-height: calc((var(--ae-extra-networks-card-real-size) * var(--ae-extra-networks-visible-rows)) + ( var(--ae-inside-padding-size) * 2 ) );
|
||||||
|
--ae-extra-networks-name-size: calc(var(--ae-extra-networks-card-size) * 1em);
|
||||||
|
|
||||||
|
--ae-top-header-padding-top:16px;
|
||||||
|
--ae-top-header-padding-bottom:16px;
|
||||||
|
--ae-top-header-inner-height:38px;
|
||||||
|
--ae-top-header-height: calc( var(--ae-top-header-padding-top) + var(--ae-top-header-inner-height) + var(--ae-top-header-padding-bottom) );
|
||||||
|
|
||||||
|
--ae-container-padding:16px;
|
||||||
|
--ae-footer-height: calc( 32px + (var(--ae-container-padding) * 2) );
|
||||||
|
--ae-gallery-bottom-height: calc(24px + (var(--ae-max-padding) * 2) + 16px + (var(--ae-inside-padding-size) * 2) + (var(--ae-outside-gap-size)* 3 ));
|
||||||
|
|
||||||
|
--ae-subtract-total: calc( var(--ae-top-header-height) + var(--ae-footer-height));
|
||||||
|
--ae-container-height : calc(100vh - var(--ae-subtract-total));
|
||||||
|
--ae-container-total-height : calc( var(--ae-container-height) - (var(--ae-outside-gap-size) * 2) - (var(--ae-inside-padding-size) * 2));
|
||||||
|
--ae-container-height-gap : calc( var(--ae-container-height) - (var(--ae-outside-gap-size) * 2));
|
||||||
|
--ae-container-height-pad : calc( var(--ae-container-height) - (var(--ae-inside-padding-size) * 2));
|
||||||
|
|
||||||
|
|
||||||
|
--ae-processing-border : 2px;
|
||||||
|
--ae-processing-border-double: var(--ae-processing-border) * 2;
|
||||||
|
|
||||||
|
--ae-slider-bg-overlay : transparent;
|
||||||
|
|
||||||
|
|
||||||
|
--ae-border-width: 1px;
|
||||||
|
--ae-accordion-vertical-padding: max(8px, var(--ae-inside-padding-size));
|
||||||
|
--ae-accordion-horizontal-padding: max(4px, var(--ae-inside-padding-size));
|
||||||
|
--ae-accordion-line-height: 24px;
|
||||||
|
--ae-accordion-header-height: calc(var(--ae-accordion-line-height) + var(--ae-accordion-vertical-padding) * 2);
|
||||||
|
|
||||||
|
--ae-results-height: calc(100vh - (var(--ae-top-header-height) + var(--ae-footer-height) + var(--ae-accordion-header-height) + var(--ae-outside-gap-size) * 4 + 38px));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
:root{
|
||||||
|
--ae-main-bg-color:hsl(0deg 0% 10%);
|
||||||
|
--ae-primary-color:hsl(168deg 96% 42%);
|
||||||
|
--ae-input-bg-color:hsl(225deg 6% 13%);
|
||||||
|
--ae-input-border-color:hsl(214deg 5% 30%);
|
||||||
|
--ae-panel-bg-color:hsl(225deg 5% 17%);
|
||||||
|
--ae-panel-border-color:hsl(214deg 5% 30%);
|
||||||
|
--ae-panel-border-radius:0px;
|
||||||
|
--ae-panel-border-width:1px;
|
||||||
|
--ae-subgroup-bg-color:hsl(0deg 0% 10%);
|
||||||
|
--ae-subgroup-input-bg-color:hsl(225deg 6% 13%);
|
||||||
|
--ae-subgroup-input-border-color:hsl(214deg 5% 30%);
|
||||||
|
--ae-subpanel-bg-color:hsl(220deg 4% 14%);
|
||||||
|
--ae-subpanel-border-color:hsl(214deg 5% 30%);
|
||||||
|
--ae-subpanel-border-radius:8px;
|
||||||
|
--ae-textarea-focus-color:hsl(210deg 3% 36%);
|
||||||
|
--ae-input-focus-color:hsl(168deg 97% 41%);
|
||||||
|
--ae-outside-gap-size:8px;
|
||||||
|
--ae-inside-padding-size:8px;
|
||||||
|
--ae-tool-button-size:34px;
|
||||||
|
--ae-tool-button-radius:16px;
|
||||||
|
--ae-generate-button-height:70px;
|
||||||
|
--ae-cancel-color:hsl(0deg 84% 60%);
|
||||||
|
--ae-max-padding:max(var(--ae-outside-gap-size),var(--ae-inside-padding-size));
|
||||||
|
--ae-icon-color:hsl(168deg 96% 42%);
|
||||||
|
--ae-icon-hover-color:hsl(0deg 0% 10%);
|
||||||
|
--ae-icon-size:22px;
|
||||||
|
--ae-nav-bg-color:hsl(0deg 0% 4%);
|
||||||
|
--ae-nav-color:hsl(210deg 4% 80%);
|
||||||
|
--ae-nav-hover-color:hsl(0deg 0% 4%);
|
||||||
|
--ae-input-color:hsl(210deg 4% 80%);
|
||||||
|
--ae-label-color:hsl(210deg 4% 80%);
|
||||||
|
--ae-subgroup-input-color:hsl(0deg 100% 100%);
|
||||||
|
--ae-placeholder-color:hsl(214deg 5% 30%);
|
||||||
|
--ae-text-color:hsl(210deg 4% 80%);
|
||||||
|
--ae-mobile-outside-gap-size:3px;
|
||||||
|
--ae-mobile-inside-padding-size:3px;
|
||||||
|
--ae-frame-bg-color:hsl(225deg 6% 13%);
|
||||||
|
--ae-modal-bg-color:hsl(0deg 0% 10%);
|
||||||
|
--ae-modal-icon-color:hsl(168deg 97% 41%);
|
||||||
|
--ae-selected-color:hsl(42deg, 100%, 42%);
|
||||||
|
}/*BREAKPOINT_CSS_CONTENT*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--toastColor: var(--ae-text-color);
|
||||||
|
--toastBackground: var(--ae-panel-bg-color);
|
||||||
|
--toastBorder: 1px solid var(--ae-panel-border-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 860px) {
|
||||||
|
:root{
|
||||||
|
--ae-outside-gap-size: var(--ae-mobile-outside-gap-size);
|
||||||
|
--ae-inside-padding-size: var(--ae-mobile-inside-padding-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--ae-main-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
padding: var(--size-4);
|
||||||
|
padding-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh !important;
|
||||||
|
min-width: unset !important;
|
||||||
|
max-width: unset !important;
|
||||||
|
background-color: var(--ae-main-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-pane {
|
||||||
|
gap: var(--ae-outside-gap-size) !important;
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
border-color: var(--ae-subpanel-border-color) !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: var(--ae-subpanel-bg-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
&.selected {
|
||||||
|
background: var(--ae-primary-color) !important;
|
||||||
|
> .block.padded {
|
||||||
|
background: var(--ae-primary-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .block {
|
||||||
|
background: var(--ae-panel-bg-color) !important;
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
}
|
||||||
|
&.z-index0 {
|
||||||
|
> .block {
|
||||||
|
background: var(--ae-main-bg-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.z-index1, &.z-index2 {
|
||||||
|
// padding: var(--ae-outside-gap-size);
|
||||||
|
// border: 1px solid var(--ae-panel-border-color);
|
||||||
|
> .block {
|
||||||
|
background: var(--ae-frame-bg-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.edit) {
|
||||||
|
&.z-index1 > .block {
|
||||||
|
padding: calc(var(--ae-outside-gap-size) / 2) !important;
|
||||||
|
border-width: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .block {
|
||||||
|
border: solid var(--ae-panel-border-width) var(--ae-panel-border-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
&.handle-hidden {
|
||||||
|
background-color: hsla(0deg 84% 60% / 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-wrapper > .widget:not(.selected) {
|
||||||
|
background: var(--ae-panel-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
.z-index0, .z-index1, .z-index2 {
|
||||||
|
> .block > .v-pane > .animation-wrapper > .widget:not(.edit) {
|
||||||
|
padding: var(--ae-inside-padding-size) !important;
|
||||||
|
border: 1px solid var(--ae-panel-border-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.widget:has(> .gradio-button) {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.gradio-button {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-gallery > .block {
|
||||||
|
background: var(--ae-main-bg-color);
|
||||||
|
border-color: var(--ae-panel-border-color);
|
||||||
|
border-radius: 0px;
|
||||||
|
|
||||||
|
.thumbnail-item {
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid var(--ae-panel-border-color) !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: var(--ae-main-bg-color)!important;
|
||||||
|
aspect-ratio: unset !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
object-fit: contain !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--ae-input-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
background: var(--ae-main-bg-color);
|
||||||
|
|
||||||
|
button {
|
||||||
|
outline: none!important;
|
||||||
|
box-shadow: none!important;
|
||||||
|
border: 1px solid var(--ae-input-border-color)!important;
|
||||||
|
border-radius: var(--ae-panel-border-radius)!important;
|
||||||
|
background: var(--ae-input-bg-color)!important;
|
||||||
|
text-align: left!important;
|
||||||
|
min-width: unset;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--ae-input-color)!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-gallery .preview.fixed-height {
|
||||||
|
height: auto;
|
||||||
|
min-height: auto;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
max-height: calc(var(--container-height) - 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* small info upload*/
|
||||||
|
div.float {
|
||||||
|
background: var(--ae-main-bg-color)!important;
|
||||||
|
border: 0 !important;
|
||||||
|
color: var(--ae-primary-color)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget {
|
||||||
|
&.selected {
|
||||||
|
background: var(--ae-primary-color) !important;
|
||||||
|
}
|
||||||
|
&.edit:not(.selected) {
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: var(--ae-primary-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-name {
|
||||||
|
background: var(--ae-subpanel-bg-color) !important;
|
||||||
|
border-color: var(--ae-subpanel-border-color) !important;
|
||||||
|
|
||||||
|
.title, .type {
|
||||||
|
color: var(--ae-label-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
background: var(--ae-panel-bg-color) !important;
|
||||||
|
border-color: var(--ae-panel-border-color) !important;
|
||||||
|
|
||||||
|
.title, .type {
|
||||||
|
color: var(--ae-label-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.props-entry {
|
||||||
|
border-width: 1px;
|
||||||
|
border-left: 1px var(--ae-panel-border-color) !important;
|
||||||
|
border-right: 1px var(--ae-panel-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .container > .block {
|
||||||
|
// box-shadow: none !important;
|
||||||
|
// border-color: var(--ae-panel-border-color) !important;
|
||||||
|
// border-radius: 0 !important;
|
||||||
|
// background: var(--ae-input-bg-color) !important;
|
||||||
|
|
||||||
|
// > .v-pane {
|
||||||
|
// box-shadow: none !important;
|
||||||
|
// border-color: var(--ae-panel-border-color) !important;
|
||||||
|
// border-radius: 0 !important;
|
||||||
|
// background: var(--ae-input-bg-color) !important;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
.block.gradio-accordion:hover .label-wrap {
|
||||||
|
color: var(--ae-main-bg-color) !important;
|
||||||
|
background-color: var(--ae-primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.gradio-accordion > div.wrap {
|
||||||
|
pointer-events: all !important;
|
||||||
|
cursor: pointer;
|
||||||
|
width: auto !important;
|
||||||
|
height: var(--ae-accordion-header-height)!important;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.form>.gradio-row>.form{
|
||||||
|
border:0 !important;
|
||||||
|
}
|
||||||
|
.padded {
|
||||||
|
padding: var(--ae-inside-padding-size) !important
|
||||||
|
}
|
||||||
|
.gradio-row,
|
||||||
|
.gap {
|
||||||
|
gap: var(--ae-outside-gap-size) !important
|
||||||
|
}
|
||||||
|
button.tool {
|
||||||
|
max-width: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
min-width: 34px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.block.padded {
|
||||||
|
/*box-shadow: var(--block-shadow);*/
|
||||||
|
border-width: var(--ae-border-width);
|
||||||
|
border-color: var(--ae-panel-border-color);
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
background: var(--ae-panel-bg-color);
|
||||||
|
/*width: 100%;
|
||||||
|
line-height: var(--line-sm);*/
|
||||||
|
}
|
||||||
|
fieldset.block.padded
|
||||||
|
{
|
||||||
|
background-color: var(--ae-panel-bg-color) !important;
|
||||||
|
/*border-width: var(--ae-border-width) !important;*/
|
||||||
|
/*border-color: var(--ae-panel-border-color) !important;*/
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.svelte-b6y5bg,
|
||||||
|
div.gradio-row>.form{
|
||||||
|
/*box-shadow: var(--block-shadow);*/
|
||||||
|
border-width: var(--ae-border-width) !important;
|
||||||
|
border-color: var(--ae-panel-border-color) !important;
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
background: var(--ae-panel-border-color) !important;
|
||||||
|
/*width: 100%;
|
||||||
|
line-height: var(--line-sm);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.gradio-dropdown,
|
||||||
|
.block.gradio-slider,
|
||||||
|
.block.gradio-checkbox,
|
||||||
|
.block.gradio-textbox,
|
||||||
|
.block.gradio-radio,
|
||||||
|
.block.gradio-checkboxgroup,
|
||||||
|
.block.gradio-number,
|
||||||
|
.block.gradio-colorpicker
|
||||||
|
{
|
||||||
|
border-width: 0;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper.gradio-textbox textarea {
|
||||||
|
overflow-y: scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-dropdown input{
|
||||||
|
margin:0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.gradio-dropdown span.single-select{
|
||||||
|
color: var(--ae-input-color)!important;
|
||||||
|
}
|
||||||
|
.dropdown-arrow.svelte-p5edak {
|
||||||
|
fill: var(--ae-input-color)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap.svelte-1p9xokt.svelte-1p9xokt.svelte-1p9xokt label,
|
||||||
|
.wrap.svelte-1qxcj04.svelte-1qxcj04.svelte-1qxcj04 label,
|
||||||
|
button.tool.secondary,
|
||||||
|
button.secondary,
|
||||||
|
.gradio-dropdown label .wrap,
|
||||||
|
input[type=text],
|
||||||
|
input[type=password],
|
||||||
|
input[type=email],
|
||||||
|
textarea,
|
||||||
|
input[type=number],
|
||||||
|
select {
|
||||||
|
outline: none!important;
|
||||||
|
box-shadow: none!important;
|
||||||
|
border: 1px solid var(--ae-input-border-color)!important;
|
||||||
|
border-radius: var(--ae-panel-border-radius)!important;
|
||||||
|
background: var(--ae-input-bg-color)!important;
|
||||||
|
color: var(--ae-input-color)!important;
|
||||||
|
text-align: left!important;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:after{
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-top: 6px solid #f00;
|
||||||
|
position: absolute;
|
||||||
|
top: 40%;
|
||||||
|
right: 5px;
|
||||||
|
content: "";
|
||||||
|
z-index: 98;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.tool.secondary,
|
||||||
|
button.secondary{
|
||||||
|
text-align: center!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-container-3-28-1 .prose * {
|
||||||
|
color: var(--ae-label-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-container-3-23-0 .prose code {
|
||||||
|
background-color: var(--ae-panel-bg-color);
|
||||||
|
border-radius: var(--ae-panel-bg-color);
|
||||||
|
border: 1px solid var(--ae-panel-border-color);
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0!important;
|
||||||
|
white-space: break-spaces !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=text],
|
||||||
|
[type=email],
|
||||||
|
[type=url],
|
||||||
|
[type=password],
|
||||||
|
[type=number],
|
||||||
|
[type=date],
|
||||||
|
[type=datetime-local],
|
||||||
|
[type=month],
|
||||||
|
[type=search],
|
||||||
|
[type=tel],
|
||||||
|
[type=time],
|
||||||
|
[type=week],
|
||||||
|
[multiple],
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.tool.secondary,
|
||||||
|
button.secondary,
|
||||||
|
.gradio-dropdown label .wrap,
|
||||||
|
input[type=text],
|
||||||
|
input[type=password],
|
||||||
|
input[type=email],
|
||||||
|
textarea,
|
||||||
|
input[type=number] {
|
||||||
|
outline: none!important;
|
||||||
|
box-shadow: none!important;
|
||||||
|
border: 1px solid var(--ae-input-border-color)!important;
|
||||||
|
border-radius: var(--ae-panel-border-radius)!important;
|
||||||
|
background: var(--ae-input-bg-color)!important;
|
||||||
|
color: var(--ae-input-color)!important;
|
||||||
|
text-align: left!important;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox], input[type=radio] {
|
||||||
|
background-color: var(--ae-input-bg-color) !important;
|
||||||
|
border: 1px solid var(--ae-input-border-color) !important;
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
}
|
||||||
|
input[type=checkbox]:checked, input[type=radio]:checked {
|
||||||
|
background-color: var(--ae-primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-slider input[type=number] {
|
||||||
|
padding-right: 2px!important;
|
||||||
|
max-height:24px !important;
|
||||||
|
// width: 64px !important;
|
||||||
|
margin-bottom: var(--ae-inside-padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.gradio-dropdown:not(.multiselect) .wrap-inner{
|
||||||
|
padding: 0px 5px !important;
|
||||||
|
height:32px !important;
|
||||||
|
}
|
||||||
|
fieldset span,
|
||||||
|
label > span{
|
||||||
|
color:var(--ae-label-color) !important;
|
||||||
|
}
|
||||||
|
.gradio-radio label > span{
|
||||||
|
color:var(--ae-input-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number],
|
||||||
|
input[type=text],
|
||||||
|
input[type=password],
|
||||||
|
input[type=email] {
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
.gradio-slider input[type=range] {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.svelte-1gfkn6j:not(.has-info) {
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-bottom: var(--ae-inside-padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* input column alignment */
|
||||||
|
label.block{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
div.block.padded.gradio-slider {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********/
|
||||||
|
/* Buttons */
|
||||||
|
/***********/
|
||||||
|
|
||||||
|
button.secondary,
|
||||||
|
button.primary {
|
||||||
|
border: 1px solid var(--ae-input-border-color) !important;
|
||||||
|
border-radius: var(--ae-panel-border-radius) !important;
|
||||||
|
background: var(--ae-input-bg-color) !important;
|
||||||
|
color: var(--ae-input-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover,
|
||||||
|
button.primary:hover {
|
||||||
|
background: var(--ae-primary-color) !important;
|
||||||
|
color: var(--ae-input-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
/* Sliders Scrollbars */
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id$="2img_settings_scroll"]::-webkit-scrollbar
|
||||||
|
{
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
box-shadow: inset 0 0 10px 10px var(--ae-main-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
box-shadow: inset 0 0 10px 10px var(--ae-panel-bg-color);
|
||||||
|
|
||||||
|
&.horizontal:hover, &.vertical:hover {
|
||||||
|
background: var(--ae-primary-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb,
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
border-left: solid 6px var(--ae-main-bg-color);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********/
|
||||||
|
/* Ranges */
|
||||||
|
/**********/
|
||||||
|
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
input[type=range] {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
input[type=range]::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 13px;
|
||||||
|
background-image: var(--ae-slider-bg-overlay);
|
||||||
|
opacity: 0.15;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type=range]::-webkit-slider-runnable-track {
|
||||||
|
height: 14px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
color: var(--ae-primary-color);
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-webkit-slider-thumb {
|
||||||
|
width: 0px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 14px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
box-shadow: -1024px 0 0 1024px var(--ae-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[id$="_sub-group"] input[type=range]
|
||||||
|
{
|
||||||
|
|
||||||
|
background-color: var(--ae-subgroup-input-bg-color);
|
||||||
|
border: 1px solid var(--ae-subgroup-input-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
|
||||||
|
input[type=range]::-moz-range-progress {
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
height: 14px;
|
||||||
|
border: 1px solid var(--ae-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-moz-range-track {
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 13px;
|
||||||
|
background-image: var(--ae-slider-bg-overlay);
|
||||||
|
opacity: 0.15;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quicksettings_overflow_container,
|
||||||
|
#theme_overflow_container,
|
||||||
|
[id$="2img_checkpoints_cards"],
|
||||||
|
[id$="2img_results"],
|
||||||
|
[id$="2img_settings_scroll"]
|
||||||
|
{
|
||||||
|
scrollbar-color: var(--ae-panel-bg-color) var(--ae-main-bg-color) !important;
|
||||||
|
scrollbar-width: thin !important;
|
||||||
|
/*padding: 0 1px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type=range]{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
background-image: var(--ae-slider-bg-overlay);
|
||||||
|
opacity: 0.15;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-moz-range-thumb {
|
||||||
|
border: 0px solid var(--ae-primary-color);
|
||||||
|
width: 0px;
|
||||||
|
border-radius: 0%;
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*hide the outline behind the border*/
|
||||||
|
input[type=range]:-moz-focusring{
|
||||||
|
outline: 1px solid var(--ae-primary-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]:focus::-moz-range-track {
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
input[type="number"]:hover,
|
||||||
|
input[type="number"]:focus {
|
||||||
|
-moz-appearance: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* IE maybe later */
|
||||||
|
|
||||||
|
input[type=range]::-ms-fill-lower {
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-ms-fill-upper {
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes light-up {
|
||||||
|
from {
|
||||||
|
background-color: var(--ae-selected-color);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-color: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfy-combo.updated {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitpanes.comfy .splitpanes__splitter {
|
||||||
|
background: var(--ae-panel-bg-color);
|
||||||
|
border: 1px solid var(--ae-panel-border-color);
|
||||||
|
|
||||||
|
&:hover:not([disabled]) {
|
||||||
|
background: var(--ae-primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue .bottom {
|
||||||
|
color: var(--ae-text-color);
|
||||||
|
> .queue-remaining {
|
||||||
|
background-color: var(--ae-panel-bg-color);
|
||||||
|
border: 1px solid var(--ae-panel-border-color);
|
||||||
|
}
|
||||||
|
> .node-name {
|
||||||
|
background-color: var(--ae-panel-bg-color);
|
||||||
|
border: 1px solid var(--ae-panel-border-color);
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
background-color: var(--ae-panel-bg-color);
|
||||||
|
border: 1px solid var(--ae-panel-border-color);
|
||||||
|
> .bar {
|
||||||
|
color: var(--ae-main-bg-color);
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
|
||||||
|
> .label {
|
||||||
|
color: var(--ae-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget > .wrapper.comfy-combo .svelte-select {
|
||||||
|
--font-size: 13px;
|
||||||
|
--height: 24px;
|
||||||
|
--input-padding: 0px;
|
||||||
|
--chevron-width: 24px;
|
||||||
|
--chevron-height: 24px;
|
||||||
|
--padding: 0 0 0 8px;
|
||||||
|
|
||||||
|
background: var(--ae-input-bg-color) !important;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
border-color: var(--ae-input-border-color) !important;
|
||||||
|
> .svelte-select-list {
|
||||||
|
background: var(--ae-panel-bg-color);
|
||||||
|
border-radius: 0px !important;
|
||||||
|
> .list-item > .item {
|
||||||
|
border-radius: 0px !important;
|
||||||
|
color: var(--ae-input-color) !important;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--ae-input-focus-color);
|
||||||
|
color: var(--ae-input-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hover:not(.active) {
|
||||||
|
background: var(--ae-textarea-focus-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .value-container {
|
||||||
|
> .selected-item {
|
||||||
|
color: var(--ae-input-color) !important;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
> input {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.chevron {
|
||||||
|
color: var(--ae-input-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/screenshot2.png
Normal file
BIN
static/screenshot2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -3,12 +3,25 @@ import { defineConfig } from 'vitest/config';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||||
import FullReload from 'vite-plugin-full-reload';
|
import FullReload from 'vite-plugin-full-reload';
|
||||||
|
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte(),
|
FullReload(["src/**/*.{js,ts,scss,svelte}"]),
|
||||||
// FullReload(["src/**/*.{js,ts,svelte}"])
|
svelte(), ,
|
||||||
|
viteStaticCopy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: 'bin/run.sh',
|
||||||
|
dest: './'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'bin/run.bat',
|
||||||
|
dest: './'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user