Improve combo refresh logic

This commit is contained in:
space-nuko
2023-05-10 15:35:22 -05:00
parent 9097705a1a
commit 7aa17581d0
7 changed files with 204 additions and 75 deletions

View File

@@ -82,8 +82,9 @@ export default class ComfyGraph extends LGraph {
const [index, spec] = pair const [index, spec] = pair
const input = comfyNode.inputs[index] as IComfyInputSlot; const input = comfyNode.inputs[index] as IComfyInputSlot;
input.defaultWidgetNode = spec.defaultWidgetNode; input.defaultWidgetNode = spec.defaultWidgetNode;
if (spec.config) if (spec.config) {
input.config = spec.config input.config = spec.config
}
} }
} }
} }

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Block, BlockTitle } from "@gradio/atoms"; import { Block, BlockTitle } from "@gradio/atoms";
import { Accordion } from "@gradio/accordion"; import Accordion from "$lib/components/gradio/app/Accordion.svelte";
import uiState from "$lib/stores/uiState"; import uiState from "$lib/stores/uiState";
import WidgetContainer from "./WidgetContainer.svelte" import WidgetContainer from "./WidgetContainer.svelte"
@@ -50,6 +50,10 @@
children = layoutState.updateChildren(container, evt.detail.items) children = layoutState.updateChildren(container, evt.detail.items)
// Ensure dragging is stopped on drag finish // Ensure dragging is stopped on drag finish
}; };
function handleClick({ clicked }: CustomEvent<boolean>) {
navigator.vibrate(20)
}
</script> </script>
{#if container && children} {#if container && children}
@@ -100,7 +104,7 @@
</Block> </Block>
{:else} {:else}
<Block elem_classes={["gradio-accordion"]}> <Block elem_classes={["gradio-accordion"]}>
<Accordion label={container.attrs.title} bind:open={container.isOpen}> <Accordion label={container.attrs.title} bind:open={container.isOpen} on:click={handleClick}>
{#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)}
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} /> <WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
{/each} {/each}

View File

@@ -19,7 +19,7 @@ import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGra
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode"; import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
import * as widgets from "$lib/widgets/index" import * as widgets from "$lib/widgets/index"
import queueState from "$lib/stores/queueState"; import queueState from "$lib/stores/queueState";
import type { SvelteComponentDev } from "svelte/internal"; import { type SvelteComponentDev } from "svelte/internal";
import type IComfyInputSlot from "$lib/IComfyInputSlot"; import type IComfyInputSlot from "$lib/IComfyInputSlot";
import type { SerializedLayoutState } from "$lib/stores/layoutState"; import type { SerializedLayoutState } from "$lib/stores/layoutState";
import layoutState from "$lib/stores/layoutState"; import layoutState from "$lib/stores/layoutState";
@@ -27,8 +27,9 @@ 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 { tick } from "svelte";
import uiState from "$lib/stores/uiState"; import uiState from "$lib/stores/uiState";
import { download, jsonToJsObject, promptToGraphVis, workflowToGraphVis } from "$lib/utils"; import { download, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
import notify from "$lib/notify"; import notify from "$lib/notify";
import configState from "$lib/stores/configState"; import configState from "$lib/stores/configState";
@@ -72,6 +73,12 @@ export type Progress = {
max: number max: number
} }
type BackendComboNode = {
comboNode: nodes.ComfyComboNode
inputSlot: IComfyInputSlot,
backendNode: ComfyBackendNode
}
function isActiveBackendNode(node: ComfyGraphNode, tag: string | null): boolean { function isActiveBackendNode(node: ComfyGraphNode, tag: string | null): boolean {
if (!node.isBackendNode) if (!node.isBackendNode)
return false; return false;
@@ -764,47 +771,78 @@ export default class ComfyApp {
async refreshComboInNodes(flashUI: boolean = false) { async refreshComboInNodes(flashUI: boolean = false) {
const defs = await this.api.getNodeDefs(); const defs = await this.api.getNodeDefs();
for (let nodeNum in this.lGraph._nodes) { const toUpdate: BackendComboNode[] = []
const node = this.lGraph._nodes[nodeNum];
if (node.type === "ui/combo") { const isComfyComboNode = (node: LGraphNode): boolean => {
(node as nodes.ComfyComboNode).valuesForCombo = null; return node
(node as nodes.ComfyComboNode).comboRefreshed.set(true); && node.type === "ui/combo"
&& "doAutoConfig" in node;
}
// Node IDs of combo widgets attached to a backend node
let backendCombos: Set<number> = new Set()
console.debug("[refreshComboInNodes] start")
// Figure out which combo nodes to update. They need to be connected to
// an input slot on a backend node with a backend config in the input
// slot connected to.
for (const node of this.lGraph.iterateNodesInOrder()) {
if (!(node as any).isBackendNode)
continue;
const backendNode = (node as ComfyBackendNode)
const inputIndex = range(backendNode.inputs.length)
.find(i => {
const input = backendNode.inputs[i]
const inputNode = backendNode.getInputNode(i)
// Does this input autocreate a combo box on creation?
const isComfyInput = "config" in input
&& "widgetNodeType" in input
&& input.widgetNodeType === "ui/combo";
return isComfyComboNode(inputNode) && isComfyInput
});
if (inputIndex != null) {
const comboNode = backendNode.getInputNode(inputIndex) as nodes.ComfyComboNode
const inputSlot = backendNode.inputs[inputIndex] as IComfyInputSlot;
const def = defs[backendNode.type];
const hasBackendConfig = def["input"]["required"][inputSlot.name] !== undefined
console.log("hasBackendConfig", node.title, inputSlot.name, hasBackendConfig)
if (hasBackendConfig) {
backendCombos.add(comboNode.id)
toUpdate.push({ comboNode, inputSlot, backendNode })
}
} }
} }
let seen = new Set() console.debug("[refreshComboInNodes] found:", toUpdate.length, toUpdate)
for (let nodeNum in this.lGraph._nodes) { // Mark combo nodes without backend configs as being loaded already.
const node = this.lGraph._nodes[nodeNum]; for (const node of this.lGraph.iterateNodesInOrder()) {
if (isComfyComboNode(node) && !backendCombos.has(node.id)) {
const comboNode = node as nodes.ComfyComboNode;
comboNode.formatValues(comboNode.properties.values);
}
}
const def = defs[node.type]; await tick();
for (let index = 0; index < node.inputs.length; index++) { // Load definitions from the backend.
const input = node.inputs[index]; for (const { comboNode, inputSlot, backendNode } of toUpdate) {
if ("config" in input) { const def = defs[backendNode.type];
const comfyInput = input as IComfyInputSlot; const rawValues = def["input"]["required"][inputSlot.name][0];
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) { console.warn("[ComfyApp] Reconfiguring combo widget", backendNode.type, "=>", comboNode.type, inputSlot.config.values.length)
const rawValues = def["input"]["required"][comfyInput.name][0]; comboNode.doAutoConfig(inputSlot, { includeProperties: new Set(["values"]), setWidgetTitle: false })
comfyInput.config.values = rawValues; comboNode.formatValues(rawValues)
const inputNode = node.getInputNode(index) if (!inputSlot.config.values.includes(get(comboNode.value))) {
comboNode.setValue(inputSlot.config.defaultValue || inputSlot.config.values[0])
if (inputNode && "doAutoConfig" in inputNode && comfyInput.widgetNodeType === inputNode.type && !seen.has(inputNode.id)) {
seen.add(inputNode.id)
console.warn("[ComfyApp] Reconfiguring combo widget", inputNode.type, comfyInput.config.values.length)
const comfyComboNode = inputNode as nodes.ComfyComboNode;
comfyComboNode.doAutoConfig(comfyInput, { includeProperties: new Set(["values"]), setWidgetTitle: false })
comfyComboNode.formatValues(rawValues)
if (!comfyInput.config.values.includes(get(comfyComboNode.value))) {
comfyComboNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0])
}
if (flashUI)
comfyComboNode.comboRefreshed.set(true)
}
}
}
} }
} }
} }

View File

@@ -61,6 +61,10 @@
return tabName return tabName
} }
function handleSelect() {
navigator.vibrate(20)
}
</script> </script>
{#if container && children} {#if container && children}
@@ -113,7 +117,7 @@
{/if} {/if}
</Block> </Block>
{:else} {:else}
<Tabs elem_classes={["gradio-tabs"]}> <Tabs elem_classes={["gradio-tabs"]} on:select={handleSelect}>
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)} {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)}
{@const tabName = getTabName(container, i)} {@const tabName = getTabName(container, i)}
<TabItem name={tabName}> <TabItem name={tabName}>

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
export let label: string = "";
export let open: boolean = true;
const dispatch = createEventDispatcher<{
click: boolean;
}>();
function handleClick() {
open = !open;
dispatch("click", open);
}
</script>
<div on:click={handleClick} class="label-wrap" class:open>
<span>{label}</span>
<span style:transform={open ? "rotate(0)" : "rotate(90deg)"} class="icon">
</span>
</div>
<div style:display={open ? "block" : "none"}>
<slot />
</div>
<style>
span {
font-weight: var(--section-header-text-weight);
font-size: var(--section-header-text-size);
}
.label-wrap {
display: flex;
justify-content: space-between;
cursor: pointer;
width: var(--size-full);
}
.label-wrap.open {
margin-bottom: var(--size-2);
}
.icon {
transition: 150ms;
}
</style>

View File

@@ -273,9 +273,15 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
console.debug("Property copy", input, this.properties) console.debug("Property copy", input, this.properties)
this.setValue(get(this.value)) this.setValue(get(this.value))
this.onAutoConfig(input);
this.notifyPropsChanged(); this.notifyPropsChanged();
} }
onAutoConfig(input: IComfyInputSlot) {
}
notifyPropsChanged() { notifyPropsChanged() {
const layoutEntry = layoutState.findLayoutEntryForNode(this.id) const layoutEntry = layoutState.findLayoutEntryForNode(this.id)
if (layoutEntry && layoutEntry.parent) { if (layoutEntry && layoutEntry.parent) {
@@ -430,18 +436,20 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
override defaultValue = "A"; override defaultValue = "A";
override saveUserState = false; override saveUserState = false;
comboRefreshed: Writable<boolean>; // True if at least one combo box refresh has taken place
// Wait until the initial graph load for combo to be valid.
valuesForCombo: any[] | null = null; firstLoad: Writable<boolean>;
valuesForCombo: Writable<any[] | null>; // Changed when the combo box has values.
constructor(name?: string) { constructor(name?: string) {
super(name, "A") super(name, "A")
this.comboRefreshed = writable(false) this.firstLoad = writable(false)
this.valuesForCombo = writable(null)
} }
override onPropertyChanged(property: any, value: any) { override onPropertyChanged(property: any, value: any) {
if (property === "values" || property === "convertValueToLabelCode") { if (property === "values" || property === "convertValueToLabelCode") {
this.formatValues(this.properties.values) // this.formatValues(this.properties.values)
} }
} }
@@ -455,10 +463,12 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
if (this.properties.convertValueToLabelCode) if (this.properties.convertValueToLabelCode)
formatter = new Function("value", this.properties.convertValueToLabelCode) as (v: string) => string; formatter = new Function("value", this.properties.convertValueToLabelCode) as (v: string) => string;
else else
formatter = (value) => `${value}`; formatter = (value: any) => `${value}`;
let valuesForCombo = []
try { try {
this.valuesForCombo = this.properties.values.map((value, index) => { valuesForCombo = this.properties.values.map((value, index) => {
return { return {
value, value,
label: formatter(value), label: formatter(value),
@@ -468,7 +478,7 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
} }
catch (err) { catch (err) {
console.error("Failed formatting!", err) console.error("Failed formatting!", err)
this.valuesForCombo = this.properties.values.map((value, index) => { valuesForCombo = this.properties.values.map((value, index) => {
return { return {
value, value,
label: `${value}`, label: `${value}`,
@@ -477,7 +487,8 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
}) })
} }
this.comboRefreshed.set(true); this.firstLoad.set(true)
this.valuesForCombo.set(valuesForCombo);
} }
onConnectOutput( onConnectOutput(

View File

@@ -13,8 +13,8 @@
let node: ComfyComboNode | null = null; let node: ComfyComboNode | null = null;
let nodeValue: Writable<string> | null = null; let nodeValue: Writable<string> | null = null;
let propsChanged: Writable<number> | null = null; let propsChanged: Writable<number> | null = null;
let comboRefreshed: Writable<boolean> | null = null; let valuesForCombo: Writable<any[]> | null = null;
let wasComboRefreshed: boolean = false; let lastConfigured: any = null;
let option: any = null; let option: any = null;
export let debug: boolean = false; export let debug: boolean = false;
@@ -40,23 +40,26 @@
node = widget.node as ComfyComboNode node = widget.node as ComfyComboNode
nodeValue = node.value; nodeValue = node.value;
propsChanged = node.propsChanged; propsChanged = node.propsChanged;
comboRefreshed = node.comboRefreshed; valuesForCombo = node.valuesForCombo;
if ($comboRefreshed)
flashOnRefreshed();
} }
} }
$: node.valuesForCombo && updateActiveIndex(node.valuesForCombo) $: $valuesForCombo != null && updateActiveIndex($valuesForCombo)
function updateActiveIndex(values: any) { function updateActiveIndex(values: any) {
const value = $nodeValue; const value = $nodeValue;
activeIndex = values.findIndex(v => v.value === value); activeIndex = values.findIndex(v => v.value === value);
} }
$: $comboRefreshed && flashOnRefreshed(); $: $valuesForCombo != lastConfigured && flashOnRefreshed();
let lightUp = false;
function flashOnRefreshed() { function flashOnRefreshed() {
setTimeout(() => ($comboRefreshed = false), 1000); lastConfigured = $valuesForCombo
if (lastConfigured != null) {
lightUp = true;
setTimeout(() => (lightUp = false), 1000);
}
} }
function getLinkValue() { function getLinkValue() {
@@ -69,7 +72,10 @@
} }
function onFocus() { function onFocus() {
navigator.vibrate(20) // console.warn("FOCUS")
if (listOpen) {
navigator.vibrate(20)
}
} }
function onSelect(e: CustomEvent<any>) { function onSelect(e: CustomEvent<any>) {
@@ -79,7 +85,7 @@
const item = e.detail const item = e.detail
console.warn("SELECT", item, item.index) console.debug("[ComboWidget] SELECT", item, item.index)
$nodeValue = item.value; $nodeValue = item.value;
activeIndex = item.index; activeIndex = item.index;
listOpen = false; listOpen = false;
@@ -94,14 +100,14 @@
let end = 0; let end = 0;
function handleHover(index: number) { function handleHover(index: number) {
console.warn("HOV", index) // console.warn("HOV", index)
hoverItemIndex = index; hoverItemIndex = index;
} }
function handleSelect(index: number) { function handleSelect(index: number) {
console.warn("SEL", index) // console.warn("SEL", index)
navigator.vibrate(20) navigator.vibrate(20)
const item = node.valuesForCombo[index] const item = $valuesForCombo[index]
activeIndex = index; activeIndex = index;
$nodeValue = item.value $nodeValue = item.value
listOpen = false; listOpen = false;
@@ -130,13 +136,13 @@
</script> </script>
<div class="wrapper comfy-combo" class:updated={$comboRefreshed}> <div class="wrapper comfy-combo" class:updated={lightUp}>
{#key $comboRefreshed} {#key $valuesForCombo}
{#if node !== null && nodeValue !== null} {#if node !== null && nodeValue !== null}
{#if node.valuesForCombo == null} {#if $valuesForCombo == null}
<span>Loading...</span> <span>Loading...</span>
{:else} {:else}
<span>Count {node.valuesForCombo.length}</span> <span>Count {$valuesForCombo.length}</span>
<label> <label>
{#if widget.attrs.title !== ""} {#if widget.attrs.title !== ""}
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle> <BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
@@ -148,7 +154,7 @@
bind:filterText bind:filterText
bind:listOpen bind:listOpen
bind:input bind:input
items={node.valuesForCombo} items={$valuesForCombo}
disabled={isDisabled(widget)} disabled={isDisabled(widget)}
clearable={false} clearable={false}
showChevron={true} showChevron={true}
@@ -160,7 +166,7 @@
on:select={(e) => handleSelect(e.detail.index)} on:select={(e) => handleSelect(e.detail.index)}
on:blur on:blur
on:filter={onFilter}> on:filter={onFilter}>
<div class="list" slot="list" let:filteredItems> <div class="comfy-select-list" slot="list" let:filteredItems>
{#if filteredItems.length > 0} {#if filteredItems.length > 0}
<VirtualList <VirtualList
items={filteredItems} items={filteredItems}
@@ -170,20 +176,25 @@
itemSize={50} itemSize={50}
scrollToIndex={hoverItemIndex}> scrollToIndex={hoverItemIndex}>
<div slot="item" <div slot="item"
class="comfy-select-item"
let:index={i} let:index={i}
let:style let:style
{style} {style}
class="item"
class:active={activeIndex === filteredItems[i].index} class:active={activeIndex === filteredItems[i].index}
class:hover={hoverItemIndex === i} class:hover={hoverItemIndex === i}
on:click={() => handleSelect(filteredItems[i].index)} on:click={() => handleSelect(filteredItems[i].index)}
on:focus={() => handleHover(i)} on:focus={() => handleHover(i)}
on:mouseover={() => handleHover(i)}> on:mouseover={() => handleHover(i)}>
{@const item = filteredItems[i]} {@const item = filteredItems[i]}
{item.label} <span class="comfy-select-label">
{item.label}
</span>
</div> </div>
</VirtualList> </VirtualList>
<p class="details">active: {activeIndex}, hover: {hoverItemIndex}<p> {:else}
<div class="comfy-empty-list">
<span>(No items)</span>
</div>
{/if} {/if}
</div> </div>
</Select> </Select>
@@ -240,12 +251,21 @@
height: 100% height: 100%
} }
.list { .comfy-select-list {
height: 30rem; height: 300px;
width: 30rem; width: 30rem;
background-color: white; background-color: white;
.item { .comfy-empty-list {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: xx-large;
color: var(--neutral-700)
}
.comfy-select-item {
font-size: 16px; font-size: 16px;
padding: 1.2rem; padding: 1.2rem;
border: 1px solid var(--neutral-300); border: 1px solid var(--neutral-300);
@@ -253,6 +273,9 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: flex;
align-items: center;
&.hover { &.hover {
color: white; color: white;
background: var(--neutral-400); background: var(--neutral-400);
@@ -262,6 +285,10 @@
color: white; color: white;
background: var(--color-blue-500); background: var(--color-blue-500);
} }
.comfy-select-label {
}
} }
.details { .details {