keyon857 commited on
Commit
3106f09
·
1 Parent(s): 0af1a1a

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +8 -0
  2. comfyui_layerstyle/workflow/1344x768_hair.png +3 -0
  3. comfyui_layerstyle/workflow/3840x2160car.jpg +3 -0
  4. comfyui_tensorrt/readme_images/image3.png +3 -0
  5. comfyui_tensorrt/readme_images/image9.png +3 -0
  6. rgthree-comfy/docs/rgthree_advanced.png +3 -0
  7. rgthree-comfy/docs/rgthree_advanced_metadata.png +3 -0
  8. rgthree-comfy/src_web/comfyui/services/config_service.ts +40 -0
  9. rgthree-comfy/src_web/comfyui/services/context_service.ts +76 -0
  10. rgthree-comfy/src_web/comfyui/services/fast_groups_service.ts +195 -0
  11. rgthree-comfy/src_web/comfyui/services/key_events_services.ts +184 -0
  12. rgthree-comfy/src_web/comfyui/testing/comfyui_env.ts +67 -0
  13. rgthree-comfy/src_web/comfyui/testing/runner.ts +133 -0
  14. rgthree-comfy/src_web/comfyui/tests/context_dynamic_tests.ts +191 -0
  15. rgthree-comfy/src_web/common/components/base_custom_element.ts +131 -0
  16. rgthree-comfy/src_web/common/css/buttons.scss +106 -0
  17. rgthree-comfy/src_web/common/css/dialog.scss +129 -0
  18. rgthree-comfy/src_web/common/css/dialog_lora_chooser.scss +161 -0
  19. rgthree-comfy/src_web/common/css/dialog_model_info.scss +396 -0
  20. rgthree-comfy/src_web/common/css/menu.scss +108 -0
  21. rgthree-comfy/src_web/common/css/pages_base.scss +69 -0
  22. rgthree-comfy/src_web/common/media/rgthree.svg +7 -0
  23. rgthree-comfy/src_web/common/media/svgs.ts +186 -0
  24. rgthree-comfy/src_web/scripts_comfy/ui/components/button.ts +23 -0
  25. rgthree-comfy/src_web/scripts_comfy/ui/components/buttonGroup.ts +10 -0
  26. rgthree-comfy/src_web/scripts_comfy/ui/components/popup.ts +16 -0
  27. rgthree-comfy/web/comfyui/any_switch.js +69 -0
  28. rgthree-comfy/web/comfyui/base_any_input_connected_node.js +206 -0
  29. rgthree-comfy/web/comfyui/base_node.js +291 -0
  30. rgthree-comfy/web/comfyui/base_node_collector.js +51 -0
  31. rgthree-comfy/web/comfyui/base_node_mode_changer.js +100 -0
  32. rgthree-comfy/web/comfyui/base_power_prompt.js +251 -0
  33. rgthree-comfy/web/comfyui/bookmark.js +110 -0
  34. rgthree-comfy/web/comfyui/bypasser.js +45 -0
  35. rgthree-comfy/web/comfyui/comfy_ui_bar.js +100 -0
  36. rgthree-comfy/web/comfyui/config.js +368 -0
  37. rgthree-comfy/web/comfyui/constants.js +53 -0
  38. rgthree-comfy/web/comfyui/context.js +323 -0
  39. rgthree-comfy/web/comfyui/dialog_info.js +280 -0
  40. rgthree-comfy/web/comfyui/display_any.js +35 -0
  41. rgthree-comfy/web/comfyui/dynamic_context.js +253 -0
  42. rgthree-comfy/web/comfyui/dynamic_context_base.js +189 -0
  43. rgthree-comfy/web/comfyui/dynamic_context_switch.js +146 -0
  44. rgthree-comfy/web/comfyui/fast_actions_button.js +272 -0
  45. rgthree-comfy/web/comfyui/fast_groups_bypasser.js +27 -0
  46. rgthree-comfy/web/comfyui/fast_groups_muter.js +418 -0
  47. rgthree-comfy/web/comfyui/feature_group_fast_toggle.js +217 -0
  48. rgthree-comfy/web/comfyui/feature_import_individual_nodes.js +54 -0
  49. rgthree-comfy/web/comfyui/image_comparer.js +363 -0
  50. rgthree-comfy/web/comfyui/image_inset_crop.js +59 -0
.gitattributes CHANGED
@@ -277,3 +277,11 @@ comfyui_tensorrt/readme_images/image10.png filter=lfs diff=lfs merge=lfs -text
277
  comfyui_tensorrt/readme_images/image6.png filter=lfs diff=lfs merge=lfs -text
278
  comfyui_tensorrt/readme_images/image4.png filter=lfs diff=lfs merge=lfs -text
279
  comfyui_tensorrt/readme_images/image11.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
277
  comfyui_tensorrt/readme_images/image6.png filter=lfs diff=lfs merge=lfs -text
278
  comfyui_tensorrt/readme_images/image4.png filter=lfs diff=lfs merge=lfs -text
279
  comfyui_tensorrt/readme_images/image11.png filter=lfs diff=lfs merge=lfs -text
280
+ comfyui_tensorrt/readme_images/image3.png filter=lfs diff=lfs merge=lfs -text
281
+ comfyui_tensorrt/readme_images/image9.png filter=lfs diff=lfs merge=lfs -text
282
+ rgthree-comfy/docs/rgthree_advanced_metadata.png filter=lfs diff=lfs merge=lfs -text
283
+ rgthree-comfy/docs/rgthree_advanced.png filter=lfs diff=lfs merge=lfs -text
284
+ comfyui_layerstyle/workflow/1344x768_hair.png filter=lfs diff=lfs merge=lfs -text
285
+ rgthree-comfy/docs/rgthree_context.png filter=lfs diff=lfs merge=lfs -text
286
+ rgthree-comfy/docs/rgthree_context_metadata.png filter=lfs diff=lfs merge=lfs -text
287
+ comfyui_layerstyle/workflow/3840x2160car.jpg filter=lfs diff=lfs merge=lfs -text
comfyui_layerstyle/workflow/1344x768_hair.png ADDED

Git LFS Details

  • SHA256: af7b15eddc78f69f1a57e1fc06a6b4aeca8125304f1609d0807b32c83e10adbe
  • Pointer size: 132 Bytes
  • Size of remote file: 1.11 MB
comfyui_layerstyle/workflow/3840x2160car.jpg ADDED

Git LFS Details

  • SHA256: 9911c55012668f5372db9b0724b39ef63bc28f2b970b231ba3c4d92beb791f99
  • Pointer size: 131 Bytes
  • Size of remote file: 396 kB
comfyui_tensorrt/readme_images/image3.png ADDED

Git LFS Details

  • SHA256: 06e2a2d5d3b33adcab26b1752554b9fc5bc7b02dedb2cd70c479f16fb3c1000a
  • Pointer size: 131 Bytes
  • Size of remote file: 262 kB
comfyui_tensorrt/readme_images/image9.png ADDED

Git LFS Details

  • SHA256: 0fcbc2f1899216fb500456c5e20a6bffda2890e048668c5f5d906f4f079b9c0a
  • Pointer size: 131 Bytes
  • Size of remote file: 363 kB
rgthree-comfy/docs/rgthree_advanced.png ADDED

Git LFS Details

  • SHA256: 77d88a50847fa76d95a1470bf0315b035a06e3c87a8d4e8132d49d8d5b8a31ce
  • Pointer size: 131 Bytes
  • Size of remote file: 456 kB
rgthree-comfy/docs/rgthree_advanced_metadata.png ADDED

Git LFS Details

  • SHA256: c33be251bd628225b9e29249b766b45539a62a9f94f2e0085b10c469f5ef0956
  • Pointer size: 131 Bytes
  • Size of remote file: 491 kB
rgthree-comfy/src_web/comfyui/services/config_service.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // @ts-ignore
2
+ import { rgthreeConfig } from "rgthree/config.js";
3
+ import { getObjectValue, setObjectValue } from "rgthree/common/shared_utils.js";
4
+ import { rgthreeApi } from "rgthree/common/rgthree_api.js";
5
+
6
+ /**
7
+ * A singleton service exported as `SERVICE` to handle configuration routines.
8
+ */
9
+ class ConfigService extends EventTarget {
10
+ getConfigValue(key: string, def?: any) {
11
+ return getObjectValue(rgthreeConfig, key, def);
12
+ }
13
+
14
+ getFeatureValue(key: string, def?: any) {
15
+ key = "features." + key.replace(/^features\./, "");
16
+ return getObjectValue(rgthreeConfig, key, def);
17
+ }
18
+
19
+ /**
20
+ * Given an object of key:value changes it will send to the server and wait for a successful
21
+ * response before setting the values on the local rgthreeConfig.
22
+ */
23
+ async setConfigValues(changed: { [key: string]: any }) {
24
+ const body = new FormData();
25
+ body.append("json", JSON.stringify(changed));
26
+ const response = await rgthreeApi.fetchJson("/config", { method: "POST", body });
27
+ if (response.status === "ok") {
28
+ for (const [key, value] of Object.entries(changed)) {
29
+ setObjectValue(rgthreeConfig, key, value);
30
+ this.dispatchEvent(new CustomEvent("config-change", { detail: { key, value } }));
31
+ }
32
+ } else {
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+ }
38
+
39
+ /** The ConfigService singleton. */
40
+ export const SERVICE = new ConfigService();
rgthree-comfy/src_web/comfyui/services/context_service.ts ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type {DynamicContextNodeBase} from "../dynamic_context_base.js";
2
+
3
+ import {app} from "scripts/app.js";
4
+ import {NodeTypesString} from "../constants.js";
5
+ import {getConnectedOutputNodesAndFilterPassThroughs} from "../utils.js";
6
+ import {INodeInputSlot, INodeOutputSlot, INodeSlot, LGraphNode} from "typings/litegraph.js";
7
+
8
+ export let SERVICE: ContextService;
9
+
10
+ const OWNED_PREFIX = "+";
11
+ const REGEX_PREFIX = /^[\+⚠️]\s*/;
12
+ const REGEX_EMPTY_INPUT = /^\+\s*$/;
13
+
14
+ export function stripContextInputPrefixes(name: string) {
15
+ return name.replace(REGEX_PREFIX, "");
16
+ }
17
+
18
+ export function getContextOutputName(inputName: string) {
19
+ if (inputName === "base_ctx") return "CONTEXT";
20
+ return stripContextInputPrefixes(inputName).toUpperCase();
21
+ }
22
+
23
+ export enum InputMutationOperation {
24
+ "UNKNOWN",
25
+ "ADDED",
26
+ "REMOVED",
27
+ "RENAMED",
28
+ }
29
+
30
+ export type InputMutation = {
31
+ operation: InputMutationOperation;
32
+ node: DynamicContextNodeBase;
33
+ slotIndex: number;
34
+ slot: INodeSlot;
35
+ };
36
+
37
+ export class ContextService {
38
+
39
+ constructor() {
40
+ if (SERVICE) {
41
+ throw new Error("ContextService was already instantiated.");
42
+ }
43
+ }
44
+
45
+ onInputChanges(node: any, mutation: InputMutation) {
46
+ const childCtxs = getConnectedOutputNodesAndFilterPassThroughs(
47
+ node,
48
+ node,
49
+ 0,
50
+ ) as DynamicContextNodeBase[];
51
+ for (const childCtx of childCtxs) {
52
+ childCtx.handleUpstreamMutation(mutation);
53
+ }
54
+ }
55
+
56
+ getDynamicContextInputsData(node: DynamicContextNodeBase) {
57
+ return node
58
+ .getContextInputsList()
59
+ .map((input: INodeInputSlot, index: number) => ({
60
+ name: stripContextInputPrefixes(input.name),
61
+ type: String(input.type),
62
+ index,
63
+ }))
64
+ .filter((i) => i.type !== "*");
65
+ }
66
+
67
+ getDynamicContextOutputsData(node: LGraphNode) {
68
+ return node.outputs.map((output: INodeOutputSlot, index: number) => ({
69
+ name: stripContextInputPrefixes(output.name),
70
+ type: String(output.type),
71
+ index,
72
+ }));
73
+ }
74
+ }
75
+
76
+ SERVICE = new ContextService();
rgthree-comfy/src_web/comfyui/services/fast_groups_service.ts ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "scripts/app.js";
2
+ import type { BaseFastGroupsModeChanger } from "../fast_groups_muter.js";
3
+ import {
4
+ type LGraph as TLGraph,
5
+ type LGraphCanvas as TLGraphCanvas,
6
+ LGraphGroup,
7
+ Vector4,
8
+ } from "typings/litegraph.js";
9
+
10
+ /**
11
+ * A service that keeps global state that can be shared by multiple FastGroupsMuter or
12
+ * FastGroupsBypasser nodes rather than calculate it on it's own.
13
+ */
14
+ class FastGroupsService {
15
+ private msThreshold = 400;
16
+ private msLastUnsorted = 0;
17
+ private msLastAlpha = 0;
18
+ private msLastPosition = 0;
19
+
20
+ private groupsUnsorted: LGraphGroup[] = [];
21
+ private groupsSortedAlpha: LGraphGroup[] = [];
22
+ private groupsSortedPosition: LGraphGroup[] = [];
23
+
24
+ private readonly fastGroupNodes: BaseFastGroupsModeChanger[] = [];
25
+
26
+ private runScheduledForMs: number | null = null;
27
+ private runScheduleTimeout: number | null = null;
28
+ private runScheduleAnimation: number | null = null;
29
+
30
+ private cachedNodeBoundings: { [key: number]: Vector4 } | null = null;
31
+
32
+ constructor() {
33
+ // Don't need to do anything, wait until a signal.
34
+ }
35
+
36
+ addFastGroupNode(node: BaseFastGroupsModeChanger) {
37
+ this.fastGroupNodes.push(node);
38
+ // Schedule it because the node may not be ready to refreshWidgets (like, when added it may
39
+ // not have cloned properties to filter against, etc.).
40
+ this.scheduleRun(8);
41
+ }
42
+
43
+ removeFastGroupNode(node: BaseFastGroupsModeChanger) {
44
+ const index = this.fastGroupNodes.indexOf(node);
45
+ if (index > -1) {
46
+ this.fastGroupNodes.splice(index, 1);
47
+ }
48
+ // If we have no more group nodes, then clear out data; it could be because of a canvas clear.
49
+ if (!this.fastGroupNodes?.length) {
50
+ this.clearScheduledRun();
51
+ this.groupsUnsorted = [];
52
+ this.groupsSortedAlpha = [];
53
+ this.groupsSortedPosition = [];
54
+ }
55
+ }
56
+
57
+ private run() {
58
+ // We only run if we're scheduled, so if we're not, then bail.
59
+ if (!this.runScheduledForMs) {
60
+ return;
61
+ }
62
+ for (const node of this.fastGroupNodes) {
63
+ node.refreshWidgets();
64
+ }
65
+ this.clearScheduledRun();
66
+ this.scheduleRun();
67
+ }
68
+
69
+ private scheduleRun(ms = 500) {
70
+ // If we got a request for an immediate schedule and already have on scheduled for longer, then
71
+ // cancel the long one to expediate a fast one.
72
+ if (this.runScheduledForMs && ms < this.runScheduledForMs) {
73
+ this.clearScheduledRun();
74
+ }
75
+ if (!this.runScheduledForMs && this.fastGroupNodes.length) {
76
+ this.runScheduledForMs = ms;
77
+ this.runScheduleTimeout = setTimeout(() => {
78
+ this.runScheduleAnimation = requestAnimationFrame(() => this.run());
79
+ }, ms);
80
+ }
81
+ }
82
+
83
+ private clearScheduledRun() {
84
+ this.runScheduleTimeout && clearTimeout(this.runScheduleTimeout);
85
+ this.runScheduleAnimation && cancelAnimationFrame(this.runScheduleAnimation);
86
+ this.runScheduleTimeout = null;
87
+ this.runScheduleAnimation = null;
88
+ this.runScheduledForMs = null;
89
+ }
90
+
91
+ /**
92
+ * Returns the boundings for all nodes on the graph, then clears it after a short delay. This is
93
+ * to increase efficiency by caching the nodes' boundings when multiple groups are on the page.
94
+ */
95
+ getBoundingsForAllNodes() {
96
+ if (!this.cachedNodeBoundings) {
97
+ this.cachedNodeBoundings = {};
98
+ for (const node of app.graph._nodes) {
99
+ this.cachedNodeBoundings[node.id] = node.getBounding();
100
+ }
101
+ setTimeout(() => {
102
+ this.cachedNodeBoundings = null;
103
+ }, 50);
104
+ }
105
+ return this.cachedNodeBoundings;
106
+ }
107
+
108
+ /**
109
+ * This overrides `LGraphGroup.prototype.recomputeInsideNodes` to be much more efficient when
110
+ * calculating for many groups at once (only compute all nodes once in `getBoundingsForAllNodes`).
111
+ */
112
+ recomputeInsideNodesForGroup(group: LGraphGroup) {
113
+ const cachedBoundings = this.getBoundingsForAllNodes();
114
+ const nodes = group.graph._nodes;
115
+ group._nodes.length = 0;
116
+
117
+ for (const node of nodes) {
118
+ const node_bounding = cachedBoundings[node.id];
119
+ if (!node_bounding || !LiteGraph.overlapBounding(group._bounding, node_bounding)) {
120
+ continue;
121
+ }
122
+ group._nodes.push(node);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Everything goes through getGroupsUnsorted, so we only get groups once. However, LiteGraph's
128
+ * `recomputeInsideNodes` is inefficient when calling multiple groups (it iterates over all nodes
129
+ * each time). So, we'll do our own dang thing, once.
130
+ */
131
+ private getGroupsUnsorted(now: number) {
132
+ const canvas = app.canvas as TLGraphCanvas;
133
+ const graph = app.graph as TLGraph;
134
+
135
+ if (
136
+ // Don't recalculate nodes if we're moving a group (added by ComfyUI in app.js)
137
+ !canvas.selected_group_moving &&
138
+ (!this.groupsUnsorted.length || now - this.msLastUnsorted > this.msThreshold)
139
+ ) {
140
+ this.groupsUnsorted = [...graph._groups];
141
+ for (const group of this.groupsUnsorted) {
142
+ this.recomputeInsideNodesForGroup(group);
143
+ (group as any)._rgthreeHasAnyActiveNode = group._nodes.some(
144
+ (n) => n.mode === LiteGraph.ALWAYS,
145
+ );
146
+ }
147
+ this.msLastUnsorted = now;
148
+ }
149
+ return this.groupsUnsorted;
150
+ }
151
+
152
+ private getGroupsAlpha(now: number) {
153
+ const graph = app.graph as TLGraph;
154
+ if (!this.groupsSortedAlpha.length || now - this.msLastAlpha > this.msThreshold) {
155
+ this.groupsSortedAlpha = [...this.getGroupsUnsorted(now)].sort((a, b) => {
156
+ return a.title.localeCompare(b.title);
157
+ });
158
+ this.msLastAlpha = now;
159
+ }
160
+ return this.groupsSortedAlpha;
161
+ }
162
+
163
+ private getGroupsPosition(now: number) {
164
+ const graph = app.graph as TLGraph;
165
+ if (!this.groupsSortedPosition.length || now - this.msLastPosition > this.msThreshold) {
166
+ this.groupsSortedPosition = [...this.getGroupsUnsorted(now)].sort((a, b) => {
167
+ // Sort by y, then x, clamped to 30.
168
+ const aY = Math.floor(a._pos[1] / 30);
169
+ const bY = Math.floor(b._pos[1] / 30);
170
+ if (aY == bY) {
171
+ const aX = Math.floor(a._pos[0] / 30);
172
+ const bX = Math.floor(b._pos[0] / 30);
173
+ return aX - bX;
174
+ }
175
+ return aY - bY;
176
+ });
177
+ this.msLastPosition = now;
178
+ }
179
+ return this.groupsSortedPosition;
180
+ }
181
+
182
+ getGroups(sort?: string) {
183
+ const now = +new Date();
184
+ if (sort === "alphanumeric") {
185
+ return this.getGroupsAlpha(now);
186
+ }
187
+ if (sort === "position") {
188
+ return this.getGroupsPosition(now);
189
+ }
190
+ return this.getGroupsUnsorted(now);
191
+ }
192
+ }
193
+
194
+ /** The FastGroupsService singleton. */
195
+ export const SERVICE = new FastGroupsService();
rgthree-comfy/src_web/comfyui/services/key_events_services.ts ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * A service responsible for capturing keys within LiteGraph's canvas, and outside of it, allowing
3
+ * nodes and other services to confidently determine what's going on.
4
+ */
5
+ class KeyEventService extends EventTarget {
6
+ readonly downKeys: { [key: string]: boolean } = {};
7
+ readonly shiftDownKeys: { [key: string]: boolean } = {};
8
+
9
+ ctrlKey = false;
10
+ altKey = false;
11
+ metaKey = false;
12
+ shiftKey = false;
13
+
14
+ private readonly isMac: boolean = !!(
15
+ navigator.platform?.toLocaleUpperCase().startsWith("MAC") ||
16
+ (navigator as any).userAgentData?.platform?.toLocaleUpperCase().startsWith("MAC")
17
+ );
18
+
19
+ constructor() {
20
+ super();
21
+ this.initialize();
22
+ }
23
+
24
+ initialize() {
25
+ const that = this;
26
+ // [🤮] Sometimes ComfyUI and/or LiteGraph stop propagation of key events which makes it hard
27
+ // to determine if keys are currently pressed. To attempt to get around this, we'll hijack
28
+ // LiteGraph's processKey to try to get better consistency.
29
+ const processKey = LGraphCanvas.prototype.processKey;
30
+ LGraphCanvas.prototype.processKey = function (e: KeyboardEvent) {
31
+ if (e.type === "keydown" || e.type === "keyup") {
32
+ that.handleKeyDownOrUp(e);
33
+ }
34
+ return processKey.apply(this, [...arguments] as any) as any;
35
+ };
36
+
37
+ // Now that ComfyUI has more non-canvas UI (like the top bar), we listen on window as well, and
38
+ // de-dupe when we get multiple events from both window and/or LiteGraph.
39
+ window.addEventListener("keydown", (e) => {
40
+ that.handleKeyDownOrUp(e);
41
+ });
42
+ window.addEventListener("keyup", (e) => {
43
+ that.handleKeyDownOrUp(e);
44
+ });
45
+
46
+ // If we get a visibilitychange, then clear the keys since we can't listen for keys up/down when
47
+ // not visible.
48
+ document.addEventListener("visibilitychange", (e) => {
49
+ this.clearKeydowns();
50
+ });
51
+
52
+ // If we get a blur, then also clear the keys since we can't listen for keys up/down when
53
+ // blurred. This can happen w/o a visibilitychange, like a browser alert.
54
+ window.addEventListener("blur", (e) => {
55
+ this.clearKeydowns();
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Adds a new queue item, unless the last is the same.
61
+ */
62
+ handleKeyDownOrUp(e: KeyboardEvent) {
63
+ const key = e.key.toLocaleUpperCase();
64
+ // If we're already down, or already up, then ignore and don't fire.
65
+ if ((e.type === 'keydown' && this.downKeys[key] === true)
66
+ || (e.type === 'keyup' && this.downKeys[key] === undefined)) {
67
+ return;
68
+ }
69
+
70
+ this.ctrlKey = !!e.ctrlKey;
71
+ this.altKey = !!e.altKey;
72
+ this.metaKey = !!e.metaKey;
73
+ this.shiftKey = !!e.shiftKey;
74
+ if (e.type === "keydown") {
75
+ this.downKeys[key] = true;
76
+ this.dispatchCustomEvent("keydown", { originalEvent: e });
77
+
78
+ // If SHIFT is pressed down as well, then we need to keep track of this separetly to "release"
79
+ // it once SHIFT is also released.
80
+ if (this.shiftKey && key !== 'SHIFT') {
81
+ this.shiftDownKeys[key] = true;
82
+ }
83
+ } else if (e.type === "keyup") {
84
+ // See https://github.com/rgthree/rgthree-comfy/issues/238
85
+ // A little bit of a hack, but Mac reportedly does something odd with copy/paste. ComfyUI
86
+ // gobbles the copy event propagation, but it happens for paste too and reportedly 'Enter' which
87
+ // I can't find a reason for in LiteGraph/comfy. So, for Mac only, whenever we lift a Command
88
+ // (META) key, we'll also clear any other keys.
89
+ if (key === "META" && this.isMac) {
90
+ this.clearKeydowns();
91
+ } else {
92
+ delete this.downKeys[key];
93
+ }
94
+
95
+ // If we're releasing the SHIFT key, then we may also be releasing all other keys we pressed
96
+ // during the SHIFT key as well. We should get an additional keydown for them after.
97
+ if (key === 'SHIFT') {
98
+ for (const key in this.shiftDownKeys) {
99
+ delete this.downKeys[key];
100
+ delete this.shiftDownKeys[key];
101
+ }
102
+ }
103
+ this.dispatchCustomEvent("keyup", { originalEvent: e });
104
+ }
105
+
106
+ }
107
+
108
+ private clearKeydowns() {
109
+ this.ctrlKey = false;
110
+ this.altKey = false;
111
+ this.metaKey = false;
112
+ this.shiftKey = false;
113
+ for (const key in this.downKeys) delete this.downKeys[key];
114
+ }
115
+
116
+ /**
117
+ * Wraps `dispatchEvent` for easier CustomEvent dispatching.
118
+ */
119
+ private dispatchCustomEvent(event: string, detail?: any) {
120
+ if (detail != null) {
121
+ return this.dispatchEvent(new CustomEvent(event, { detail }));
122
+ }
123
+ return this.dispatchEvent(new CustomEvent(event));
124
+ }
125
+
126
+ /**
127
+ * Parses a shortcut string.
128
+ *
129
+ * - 's' => ['S']
130
+ * - 'shift + c' => ['SHIFT', 'C']
131
+ * - 'shift + meta + @' => ['SHIFT', 'META', '@']
132
+ * - 'shift + + + @' => ['SHIFT', '__PLUS__', '=']
133
+ * - '+ + p' => ['__PLUS__', 'P']
134
+ */
135
+ private getKeysFromShortcut(shortcut: string | string[]) {
136
+ let keys;
137
+ if (typeof shortcut === "string") {
138
+ // Rip all spaces out. Note, Comfy swallows space, so we don't have to handle it. Otherwise,
139
+ // we would require space to be fed as "Space" or "Spacebar" instead of " ".
140
+ shortcut = shortcut.replace(/\s/g, "");
141
+ // Change a real "+" to something we can encode.
142
+ shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
143
+ keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
144
+ } else {
145
+ keys = [...shortcut];
146
+ }
147
+ return keys.map((k) => k.toLocaleUpperCase());
148
+ }
149
+
150
+ /**
151
+ * Checks if all keys passed in are down.
152
+ */
153
+ areAllKeysDown(keys: string | string[]) {
154
+ keys = this.getKeysFromShortcut(keys);
155
+ return keys.every((k) => {
156
+ return this.downKeys[k];
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Checks if only the keys passed in are down; optionally and additionally allowing "shift" key.
162
+ */
163
+ areOnlyKeysDown(keys: string | string[], alsoAllowShift = false) {
164
+ keys = this.getKeysFromShortcut(keys);
165
+ const allKeysDown = this.areAllKeysDown(keys);
166
+ const downKeysLength = Object.values(this.downKeys).length;
167
+ // All keys are down and they're the only ones.
168
+ if (allKeysDown && keys.length === downKeysLength) {
169
+ return true;
170
+ }
171
+ // Special case allowing the shift key in addition to the shortcut keys. This helps when a user
172
+ // may had originally defined "$" as a shortcut, but needs to press "shift + $" since it's an
173
+ // upper key character, etc.
174
+ if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
175
+ // If we're holding down shift, have one extra key held down, and the original keys don't
176
+ // include shift, then we're good to go.
177
+ return allKeysDown && this.areAllKeysDown(["SHIFT"]);
178
+ }
179
+ return false;
180
+ }
181
+ }
182
+
183
+ /** The KeyEventService singleton. */
184
+ export const SERVICE = new KeyEventService();
rgthree-comfy/src_web/comfyui/testing/comfyui_env.ts ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "scripts/app.js";
2
+ import { NodeTypesString } from "../constants.js";
3
+ import { wait } from "rgthree/common/shared_utils.js";
4
+ import type { LGraphNode } from "typings/litegraph.js";
5
+
6
+ type addNodeOptions = {
7
+ placement?: string;
8
+ };
9
+
10
+ /**
11
+ * A testing environment to make setting up, clearing, and queuing more predictable in an
12
+ * integration test environment.
13
+ */
14
+ export class ComfyUITestEnvironment {
15
+ private lastNode: LGraphNode | null = null;
16
+ private maxY = 0;
17
+
18
+ constructor() {}
19
+
20
+ async addNode(nodeString: string, options: addNodeOptions = {}) {
21
+ const [canvas, graph] = [app.canvas, app.graph];
22
+ const node = LiteGraph.createNode(nodeString);
23
+ let x = 0;
24
+ let y = 30;
25
+ if (this.lastNode) {
26
+ const placement = options.placement || "right";
27
+ if (placement === "under") {
28
+ x = this.lastNode.pos[0];
29
+ y = this.lastNode.pos[1] + this.lastNode.size[1] + 30;
30
+ } else if (placement === "right") {
31
+ x = this.lastNode.pos[0] + this.lastNode.size[0] + 100;
32
+ y = this.lastNode.pos[1];
33
+ } else if (placement === "start") {
34
+ x = 0;
35
+ y = this.maxY + 50;
36
+ }
37
+ }
38
+ canvas.graph.add(node);
39
+ node.pos = [x, y];
40
+ canvas.selectNode(node);
41
+ app.graph.setDirtyCanvas(true, true);
42
+ await wait();
43
+ this.lastNode = node;
44
+ this.maxY = Math.max(this.maxY, y + this.lastNode.size[1]);
45
+ return (this.lastNode = node);
46
+ }
47
+
48
+ async clear() {
49
+ app.clean();
50
+ app.graph.clear();
51
+ const nodeConfig = await this.addNode(NodeTypesString.KSAMPLER_CONFIG);
52
+ const displayAny = await this.addNode(NodeTypesString.DISPLAY_ANY);
53
+ nodeConfig.widgets[0]!.value = Math.round(Math.random() * 100);
54
+ nodeConfig.connect(0, displayAny, 0);
55
+ await this.queuePrompt();
56
+ app.clean();
57
+ app.graph.clear();
58
+ this.lastNode = null;
59
+ this.maxY = 0;
60
+ await wait();
61
+ }
62
+
63
+ async queuePrompt() {
64
+ await app.queuePrompt();
65
+ await wait(150);
66
+ }
67
+ }
rgthree-comfy/src_web/comfyui/testing/runner.ts ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @fileoverview A set of methods that mimic a bit of the Jasmine testing library, but simpler and
3
+ * more succinct for manipulating a comfy integration test.
4
+ */
5
+ import { wait } from "rgthree/common/shared_utils.js";
6
+
7
+ type TestContext = {
8
+ label?: string;
9
+ beforeEach?: Function[];
10
+ };
11
+
12
+ let contexts: TestContext[] = [];
13
+
14
+ export function describe(label: string, fn: Function) {
15
+ return async () => {
16
+ await describeRun(label, fn);
17
+ };
18
+ }
19
+
20
+ export async function describeRun(label: string, fn: Function) {
21
+ await wait();
22
+ contexts.push({ label });
23
+ console.group(`[Start] ${contexts[contexts.length - 1]!.label}`);
24
+ await fn();
25
+ contexts.pop();
26
+ console.groupEnd();
27
+ }
28
+
29
+ export async function should(declaration: string, fn: Function) {
30
+ if (!contexts[contexts.length - 1]) {
31
+ throw Error("Called should outside of a describe.");
32
+ }
33
+ console.group(`...should ${declaration}`);
34
+ try {
35
+ for (const context of contexts) {
36
+ for (const beforeEachFn of context?.beforeEach || []) {
37
+ await beforeEachFn();
38
+ }
39
+ }
40
+ await fn();
41
+ } catch (e: any) {
42
+ fail(e);
43
+ }
44
+ console.groupEnd();
45
+ }
46
+
47
+ export async function beforeEach(fn: Function) {
48
+ if (!contexts[contexts.length - 1]) {
49
+ throw Error("Called beforeEach outside of a describe.");
50
+ }
51
+ const last = contexts[contexts.length - 1]!;
52
+ last.beforeEach = last?.beforeEach || [];
53
+ last.beforeEach.push(fn);
54
+ }
55
+
56
+ export function fail(e: Error) {
57
+ log(`X Failure: ${e}`, "color:#600; background:#fdd; padding: 2px 6px;");
58
+ }
59
+
60
+ function log(msg: string, styles: string) {
61
+ if (styles) {
62
+ console.log(`%c ${msg}`, styles);
63
+ } else {
64
+ console.log(msg);
65
+ }
66
+ }
67
+
68
+ class Expectation {
69
+ private propertyLabel: string | null = "";
70
+ private expectedLabel: string | null = "";
71
+ private expectedFn!: (v: any) => boolean;
72
+ private value: any;
73
+
74
+ constructor(value: any) {
75
+ this.value = value;
76
+ }
77
+
78
+ toBe(labelOrExpected: any, maybeExpected?: any) {
79
+ const expected = maybeExpected !== undefined ? maybeExpected : labelOrExpected;
80
+ this.propertyLabel = maybeExpected !== undefined ? labelOrExpected : null;
81
+ this.expectedLabel = JSON.stringify(expected);
82
+ this.expectedFn = (v) => v == expected;
83
+ return this.toBeEval();
84
+ }
85
+ toBeUndefined(propertyLabel: string) {
86
+ this.expectedFn = (v) => v === undefined;
87
+ this.propertyLabel = propertyLabel || "";
88
+ this.expectedLabel = "undefined";
89
+ return this.toBeEval(true);
90
+ }
91
+ toBeNullOrUndefined(propertyLabel: string) {
92
+ this.expectedFn = (v) => v == null;
93
+ this.propertyLabel = propertyLabel || "";
94
+ this.expectedLabel = "null or undefined";
95
+ return this.toBeEval(true);
96
+ }
97
+ toBeTruthy(propertyLabel: string) {
98
+ this.expectedFn = (v) => !v;
99
+ this.propertyLabel = propertyLabel || "";
100
+ this.expectedLabel = "truthy";
101
+ return this.toBeEval(false);
102
+ }
103
+ toBeANumber(propertyLabel: string) {
104
+ this.expectedFn = (v) => typeof v === "number";
105
+ this.propertyLabel = propertyLabel || "";
106
+ this.expectedLabel = "a number";
107
+ return this.toBeEval();
108
+ }
109
+ toBeEval(strict = false) {
110
+ let evaluation = this.expectedFn(this.value);
111
+ let msg = `Expected ${this.propertyLabel ? this.propertyLabel + " to be " : ""}${
112
+ this.expectedLabel
113
+ }`;
114
+ msg += evaluation ? "." : `, but was ${JSON.stringify(this.value)}`;
115
+ this.log(evaluation, msg);
116
+ return evaluation;
117
+ }
118
+ log(value: boolean, msg: string) {
119
+ if (value) {
120
+ log(`🗸 ${msg}`, "color:#060; background:#cec; padding: 2px 6px;");
121
+ } else {
122
+ log(`X ${msg}`, "color:#600; background:#fdd; padding: 2px 6px;");
123
+ }
124
+ }
125
+ }
126
+
127
+ export function expect(value: any, msg?: string) {
128
+ const expectation = new Expectation(value);
129
+ if (msg) {
130
+ expectation.log(value, msg);
131
+ }
132
+ return expectation;
133
+ }
rgthree-comfy/src_web/comfyui/tests/context_dynamic_tests.ts ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type {
2
+ LiteGraph as TLiteGraph,
3
+ LGraphCanvas as TLGraphCanvas,
4
+ LGraph as TLGraph,
5
+ LGraphNode as TLGraphNode,
6
+ Vector2,
7
+ LGraphNode,
8
+ } from "typings/litegraph.js";
9
+ import {rgthree} from "../rgthree.js";
10
+ import {NodeTypesString} from "../constants.js";
11
+ import {wait} from "rgthree/common/shared_utils.js";
12
+ import {describe, should, beforeEach, expect, describeRun} from "../testing/runner.js";
13
+ import {ComfyUITestEnvironment} from "../testing/comfyui_env.js";
14
+
15
+ declare const LiteGraph: typeof TLiteGraph;
16
+
17
+ const env = new ComfyUITestEnvironment();
18
+
19
+ function verifyInputAndOutputName(
20
+ node: LGraphNode,
21
+ index: number,
22
+ inputName: string | null,
23
+ isLinked?: boolean,
24
+ ) {
25
+ if (inputName != null) {
26
+ expect(node.inputs[index]!.name).toBe(`input ${index} name`, inputName);
27
+ }
28
+ if (isLinked) {
29
+ expect(node.inputs[index]!.link).toBeANumber(`input ${index} connection`);
30
+ } else if (isLinked === false) {
31
+ expect(node.inputs[index]!.link).toBeNullOrUndefined(`input ${index} connection`);
32
+ }
33
+ if (inputName != null) {
34
+ if (inputName === "+") {
35
+ expect(node.outputs[index]).toBeUndefined(`output ${index}`);
36
+ } else {
37
+ let outputName =
38
+ inputName === "base_ctx" ? "CONTEXT" : inputName.replace(/^\+\s/, "").toUpperCase();
39
+ expect(node.outputs[index]!.name).toBe(`output ${index} name`, outputName);
40
+ }
41
+ }
42
+ }
43
+
44
+ function vertifyInputsStructure(node: LGraphNode, expectedLength: number) {
45
+ expect(node.inputs.length).toBe("inputs length", expectedLength);
46
+ expect(node.outputs.length).toBe("outputs length", expectedLength - 1);
47
+ verifyInputAndOutputName(node, expectedLength - 1, "+", false);
48
+ }
49
+
50
+ (window as any).rgthree_tests = (window as any).rgthree_tests || {};
51
+ (window as any).rgthree_tests.test_dynamic_context = describe("ContextDynamicTest", async () => {
52
+ let nodeConfig!: TLGraphNode;
53
+ let nodeCtx!: TLGraphNode;
54
+
55
+ let lastNode: LGraphNode | null = null;
56
+
57
+ await beforeEach(async () => {
58
+ await env.clear();
59
+ lastNode = nodeConfig = await env.addNode(NodeTypesString.KSAMPLER_CONFIG);
60
+ lastNode = nodeCtx = await env.addNode(NodeTypesString.DYNAMIC_CONTEXT);
61
+ nodeConfig.connect(0, nodeCtx, 1); // steps
62
+ nodeConfig.connect(2, nodeCtx, 2); // cfg
63
+ nodeConfig.connect(4, nodeCtx, 3); // scheduler
64
+ nodeConfig.connect(0, nodeCtx, 4); // This is the step.1
65
+ nodeConfig.connect(0, nodeCtx, 5); // This is the step.2
66
+ nodeCtx.disconnectInput(2);
67
+ nodeCtx.disconnectInput(5);
68
+ nodeConfig.connect(0, nodeCtx, 6); // This is the step.3
69
+ nodeCtx.disconnectInput(6);
70
+ await wait();
71
+ });
72
+
73
+ await should("add correct inputs", async () => {
74
+ vertifyInputsStructure(nodeCtx, 8);
75
+ let i = 0;
76
+ verifyInputAndOutputName(nodeCtx, i++, "base_ctx", false);
77
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps", true);
78
+ verifyInputAndOutputName(nodeCtx, i++, "+ cfg", false);
79
+ verifyInputAndOutputName(nodeCtx, i++, "+ scheduler", true);
80
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.1", true);
81
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.2", false);
82
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.3", false);
83
+ });
84
+
85
+ await should("add evaluate correct outputs", async () => {
86
+ const displayAny1 = await env.addNode(NodeTypesString.DISPLAY_ANY, {placement: "right"});
87
+ const displayAny2 = await env.addNode(NodeTypesString.DISPLAY_ANY, {placement: "under"});
88
+ const displayAny3 = await env.addNode(NodeTypesString.DISPLAY_ANY, {placement: "under"});
89
+ const displayAny4 = await env.addNode(NodeTypesString.DISPLAY_ANY, {placement: "under"});
90
+
91
+ nodeCtx.connect(1, displayAny1, 0); // steps
92
+ nodeCtx.connect(3, displayAny2, 0); // scheduler
93
+ nodeCtx.connect(4, displayAny3, 0); // steps.1
94
+ nodeCtx.connect(6, displayAny4, 0); // steps.3 (unlinked)
95
+
96
+ await env.queuePrompt();
97
+
98
+ expect(displayAny1.widgets![0]!.value).toBe("output 1", 30);
99
+ expect(displayAny2.widgets![0]!.value).toBe("output 3", '"normal"');
100
+ expect(displayAny3.widgets![0]!.value).toBe("output 4", 30);
101
+ expect(displayAny4.widgets![0]!.value).toBe("output 6", "None");
102
+ });
103
+
104
+ await describeRun("Nested", async () => {
105
+ let nodeConfig2!: TLGraphNode;
106
+ let nodeCtx2!: TLGraphNode;
107
+
108
+ await beforeEach(async () => {
109
+ nodeConfig2 = await env.addNode(NodeTypesString.KSAMPLER_CONFIG, {placement: "start"});
110
+ nodeConfig2.widgets[0]!.value = 111;
111
+ nodeConfig2.widgets[2]!.value = 11.1;
112
+ nodeCtx2 = await env.addNode(NodeTypesString.DYNAMIC_CONTEXT, {placement: "right"});
113
+ nodeConfig2.connect(0, nodeCtx2, 1); // steps
114
+ nodeConfig2.connect(2, nodeCtx2, 2); // cfg
115
+ nodeConfig2.connect(3, nodeCtx2, 3); // sampler
116
+ nodeConfig2.connect(2, nodeCtx2, 4); // This is the cfg.1
117
+ nodeConfig2.connect(0, nodeCtx2, 5); // This is the steps.1
118
+ nodeCtx2.disconnectInput(2);
119
+ nodeCtx2.disconnectInput(5);
120
+ nodeConfig2.connect(2, nodeCtx2, 6); // This is the cfg.2
121
+ nodeCtx2.disconnectInput(6);
122
+
123
+ await wait();
124
+ });
125
+
126
+ await should("disallow context node to be connected to non-first spot.", async () => {
127
+ // Connect to first node.
128
+ let expectedInputs = 8;
129
+
130
+ nodeCtx2.connect(0, nodeCtx, expectedInputs - 1);
131
+ console.log(nodeCtx.inputs);
132
+
133
+ vertifyInputsStructure(nodeCtx, expectedInputs);
134
+ verifyInputAndOutputName(nodeCtx, 0, "base_ctx", false);
135
+ verifyInputAndOutputName(nodeCtx, nodeCtx.inputs.length - 1, null, false);
136
+
137
+ nodeCtx2.connect(0, nodeCtx, 0);
138
+ expectedInputs = 14;
139
+ vertifyInputsStructure(nodeCtx, expectedInputs);
140
+ verifyInputAndOutputName(nodeCtx, 0, "base_ctx", true);
141
+ verifyInputAndOutputName(nodeCtx, expectedInputs - 1, null, false);
142
+ });
143
+
144
+ await should("add inputs from connected above owned.", async () => {
145
+ // Connect to first node.
146
+ nodeCtx2.connect(0, nodeCtx, 0);
147
+
148
+ let expectedInputs = 14;
149
+ vertifyInputsStructure(nodeCtx, expectedInputs);
150
+ let i = 0;
151
+ verifyInputAndOutputName(nodeCtx, i++, "base_ctx", true);
152
+ verifyInputAndOutputName(nodeCtx, i++, "steps", false);
153
+ verifyInputAndOutputName(nodeCtx, i++, "cfg", false);
154
+ verifyInputAndOutputName(nodeCtx, i++, "sampler", false);
155
+ verifyInputAndOutputName(nodeCtx, i++, "cfg.1", false);
156
+ verifyInputAndOutputName(nodeCtx, i++, "steps.1", false);
157
+ verifyInputAndOutputName(nodeCtx, i++, "cfg.2", false);
158
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.2", true);
159
+ verifyInputAndOutputName(nodeCtx, i++, "+ cfg.3", false);
160
+ verifyInputAndOutputName(nodeCtx, i++, "+ scheduler", true);
161
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.3", true);
162
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.4", false);
163
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.5", false);
164
+ verifyInputAndOutputName(nodeCtx, i++, "+", false);
165
+ });
166
+
167
+ await should("add then remove inputs when disconnected.", async () => {
168
+ // Connect to first node.
169
+ nodeCtx2.connect(0, nodeCtx, 0);
170
+
171
+ let expectedInputs = 14;
172
+ expect(nodeCtx.inputs.length).toBe("inputs length", expectedInputs);
173
+ expect(nodeCtx.outputs.length).toBe("outputs length", expectedInputs - 1);
174
+
175
+ nodeCtx.disconnectInput(0);
176
+
177
+ expectedInputs = 8;
178
+ expect(nodeCtx.inputs.length).toBe("inputs length", expectedInputs);
179
+ expect(nodeCtx.outputs.length).toBe("outputs length", expectedInputs - 1);
180
+ let i = 0;
181
+ verifyInputAndOutputName(nodeCtx, i++, "base_ctx", false);
182
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps", true);
183
+ verifyInputAndOutputName(nodeCtx, i++, "+ cfg", false);
184
+ verifyInputAndOutputName(nodeCtx, i++, "+ scheduler", true);
185
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.1", true);
186
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.2", false);
187
+ verifyInputAndOutputName(nodeCtx, i++, "+ steps.3", false);
188
+ verifyInputAndOutputName(nodeCtx, i++, "+", false);
189
+ });
190
+ });
191
+ });
rgthree-comfy/src_web/common/components/base_custom_element.ts ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { $el } from "rgthree/common/utils_dom.js";
2
+
3
+ const CSS_STYLE_SHEETS = new Map<string, string>();
4
+ const HTML_TEMPLATE_FILES = new Map<string, string>();
5
+
6
+ /**
7
+ * Fetches the stylesheet for the component, matched by the element name (minus the "rgthree-"
8
+ * prefix).
9
+ */
10
+ async function getStyleSheet(name: string) {
11
+ if (!CSS_STYLE_SHEETS.has(name)) {
12
+ try {
13
+ const response = await fetch(
14
+ `rgthree/common/components/${name.replace("rgthree-", "").replace(/\-/g, "_")}.css`,
15
+ );
16
+ const text = await response.text();
17
+ CSS_STYLE_SHEETS.set(name, text);
18
+ } catch (e) {
19
+ alert("Error loading rgthree custom component css.");
20
+ }
21
+ }
22
+ return CSS_STYLE_SHEETS.get(name)!;
23
+ }
24
+
25
+ /**
26
+ * Fetches the stylesheet for the component, matched by the element name (minus the "rgthree-"
27
+ * prefix).
28
+ */
29
+ async function getTemplateMarkup(name: string) {
30
+ if (!HTML_TEMPLATE_FILES.has(name)) {
31
+ try {
32
+ const response = await fetch(
33
+ `rgthree/common/components/${name.replace("rgthree-", "").replace(/\-/g, "_")}.html`,
34
+ );
35
+ const text = await response.text();
36
+ HTML_TEMPLATE_FILES.set(name, text);
37
+ } catch (e) {
38
+ // alert("Error loading rgthree custom component markup.");
39
+ }
40
+ }
41
+ return HTML_TEMPLATE_FILES.get(name)!;
42
+ }
43
+
44
+ /**
45
+ * A base custom element.
46
+ */
47
+ export abstract class RgthreeCustomElement extends HTMLElement {
48
+ static NAME = "rgthree-override";
49
+
50
+ static create<T extends RgthreeCustomElement>(): T {
51
+ if (this.name === "rgthree-override") {
52
+ throw new Error("Must override component NAME");
53
+ }
54
+ if (!customElements.get(this.name)) {
55
+ customElements.define(this.NAME, this as unknown as CustomElementConstructor);
56
+ }
57
+ return document.createElement(this.NAME) as T;
58
+ }
59
+
60
+ protected connected: boolean = false;
61
+ protected shadow!: ShadowRoot;
62
+ protected readonly templates = new Map<string, HTMLTemplateElement>();
63
+
64
+ onFirstConnected(): void {
65
+ // Optionally overridden.
66
+ };
67
+ onReconnected(): void {
68
+ // Optionally overridden.
69
+ };
70
+ onConnected(): void {
71
+ // Optionally overridden.
72
+ };
73
+ onDisconnected(): void {
74
+ // Optionally overridden.
75
+ };
76
+
77
+ async connectedCallback() {
78
+ const elementName = (this.constructor as any).NAME as string;
79
+ const wasConnected = this.connected;
80
+ if (!wasConnected) {
81
+ this.connected = true;
82
+ }
83
+ if (!this.shadow) {
84
+ const [stylesheet, markup] = await Promise.all([
85
+ getStyleSheet(elementName),
86
+ getTemplateMarkup(elementName),
87
+ ]);
88
+
89
+ if (markup) {
90
+ const temp = $el('div')
91
+ const templatesMarkup = markup.match(/<template[^]*?<\/template>/gm) || [];
92
+ for(const markup of templatesMarkup) {
93
+ temp.innerHTML = markup;
94
+ const template = temp.children[0];
95
+ if (!(template instanceof HTMLTemplateElement)) {
96
+ throw new Error('Not a template element.');
97
+ }
98
+ const id = template.getAttribute('id');
99
+ if (!id) {
100
+ throw new Error('Not template id.');
101
+ }
102
+ this.templates.set(id, template);
103
+ }
104
+ }
105
+
106
+ this.shadow = this.attachShadow({mode: "open"});
107
+ const sheet = new CSSStyleSheet();
108
+ sheet.replaceSync(stylesheet);
109
+ this.shadow.adoptedStyleSheets = [sheet];
110
+
111
+ let template;
112
+ if (this.templates.has(elementName)) {
113
+ template = this.templates.get(elementName);
114
+ } else if (this.templates.has(elementName.replace('rgthree-', ''))) {
115
+ template = this.templates.get(elementName.replace('rgthree-', ''))
116
+ }
117
+ if (template) {
118
+ this.shadow.appendChild(template.content.cloneNode(true));
119
+ }
120
+
121
+ this.onFirstConnected();
122
+ } else {
123
+ this.onReconnected();
124
+ }
125
+ this.onConnected();
126
+ }
127
+ disconnectedCallback() {
128
+ this.connected = false;
129
+ this.onDisconnected()
130
+ }
131
+ }
rgthree-comfy/src_web/common/css/buttons.scss ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :not(#fakeid) .rgthree-button-reset {
2
+ position: relative;
3
+ appearance: none;
4
+ cursor: pointer;
5
+ border: 0;
6
+ background: transparent;
7
+ color: inherit;
8
+ padding: 0;
9
+ margin: 0;
10
+
11
+ }
12
+ :not(#fakeid) .rgthree-button {
13
+ --padding-top: 7px;
14
+ --padding-bottom: 9px;
15
+ --padding-x: 16px;
16
+ position: relative;
17
+ cursor: pointer;
18
+ border: 0;
19
+ border-radius: 0.25rem;
20
+ background: rgba(0, 0, 0, 0.5);
21
+ color: white;
22
+ font-family: system-ui, sans-serif;
23
+ font-size: calc(16rem / 16);
24
+ line-height: 1;
25
+ white-space: nowrap;
26
+ text-decoration: none;
27
+ margin: 0.25rem;
28
+ box-shadow: 0px 0px 2px rgb(0, 0, 0);
29
+ background: #212121;
30
+ transition: all 0.1s ease-in-out;
31
+ padding: var(--padding-top) var(--padding-x) var(--padding-bottom);
32
+ display: inline-flex;
33
+ flex-direction: row;
34
+ align-items: center;
35
+ justify-content: center;
36
+
37
+ &::before,
38
+ &::after {
39
+ content: "";
40
+ display: block;
41
+ position: absolute;
42
+ border-radius: 0.25rem;
43
+ left: 0;
44
+ top: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ box-shadow:
48
+ inset 1px 1px 0px rgba(255, 255, 255, 0.12),
49
+ inset -1px -1px 0px rgba(0, 0, 0, 0.75);
50
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15));
51
+ mix-blend-mode: screen;
52
+ }
53
+
54
+ &::after {
55
+ mix-blend-mode: multiply;
56
+ }
57
+
58
+ &:hover {
59
+ background: #303030;
60
+ }
61
+ &:active {
62
+ box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
63
+ background: #121212;
64
+ padding: calc(var(--padding-top) + 1px) calc(var(--padding-x) - 1px)
65
+ calc(var(--padding-bottom) - 1px) calc(var(--padding-x) + 1px);
66
+ }
67
+
68
+ &:active::before,
69
+ &:active::after {
70
+ box-shadow:
71
+ 1px 1px 0px rgba(255, 255, 255, 0.15),
72
+ inset 1px 1px 0px rgba(0, 0, 0, 0.5),
73
+ inset 1px 3px 5px rgba(0, 0, 0, 0.33);
74
+ }
75
+
76
+ &.-blue {
77
+ background: #346599 !important;
78
+ }
79
+ &.-blue:hover {
80
+ background: #3b77b8 !important;
81
+ }
82
+ &.-blue:active {
83
+ background: #1d5086 !important;
84
+ }
85
+
86
+ &.-green {
87
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #14580b;
88
+ }
89
+ &.-green:hover {
90
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #1a6d0f;
91
+ }
92
+ &.-green:active {
93
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.06)), #0f3f09;
94
+ }
95
+
96
+ &[disabled] {
97
+ box-shadow: none;
98
+ background: #666 !important;
99
+ color: #aaa;
100
+ pointer-events: none;
101
+ }
102
+ &[disabled]::before,
103
+ &[disabled]::after {
104
+ display: none;
105
+ }
106
+ }
rgthree-comfy/src_web/common/css/dialog.scss ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .rgthree-dialog {
3
+ outline: 0;
4
+ border: 0;
5
+ border-radius: 6px;
6
+ background: #414141;
7
+ color: #fff;
8
+ box-shadow:
9
+ inset 1px 1px 0px rgba(255, 255, 255, 0.05),
10
+ inset -1px -1px 0px rgba(0, 0, 0, 0.5),
11
+ 2px 2px 20px rgb(0, 0, 0);
12
+ max-width: 800px;
13
+ box-sizing: border-box;
14
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
15
+ font-size: 1rem;
16
+ padding: 0;
17
+ max-height: calc(100% - 32px);
18
+
19
+ *, *::before, *::after {
20
+ box-sizing: inherit;
21
+ }
22
+ }
23
+
24
+ .rgthree-dialog-container {
25
+ // padding: 16px;
26
+ > * {
27
+ padding: 8px 16px;
28
+
29
+ &:first-child {
30
+ padding-top: 16px;
31
+ }
32
+ &:last-child {
33
+ padding-bottom: 16px;
34
+ }
35
+ }
36
+ }
37
+
38
+ .rgthree-dialog.-iconed::after {
39
+ content: "";
40
+ font-size: 276px;
41
+ position: absolute;
42
+ right: 0px;
43
+ bottom: 0px;
44
+ opacity: 0.15;
45
+ display: block;
46
+ width: 237px;
47
+ overflow: hidden;
48
+ height: 186px;
49
+ line-height: 1;
50
+ pointer-events: none;
51
+ z-index: -1;
52
+ }
53
+ .rgthree-dialog.-iconed.-help::after {
54
+ content: "🛟";
55
+ }
56
+ .rgthree-dialog.-iconed.-settings::after {
57
+ content: "⚙️";
58
+ }
59
+
60
+ @media (max-width: 832px) {
61
+ .rgthree-dialog {
62
+ max-width: calc(100% - 32px);
63
+ }
64
+ }
65
+
66
+ .rgthree-dialog-container-title {
67
+ display: flex;
68
+ flex-direction: row;
69
+ align-items: center;
70
+ justify-content: start;
71
+ }
72
+ .rgthree-dialog-container-title > svg:first-child {
73
+ width: 36px;
74
+ height: 36px;
75
+ margin-right: 16px;
76
+ }
77
+ .rgthree-dialog-container-title h2 {
78
+ font-size: calc(22rem / 16);
79
+ margin: 0;
80
+ font-weight: bold;
81
+ }
82
+
83
+ .rgthree-dialog-container-title h2 small {
84
+ font-size: calc(13rem / 16);
85
+ font-weight: normal;
86
+ opacity: 0.75;
87
+ }
88
+
89
+ .rgthree-dialog-container-content {
90
+ overflow: auto;
91
+ max-height: calc(100vh - 200px); /* Arbitrary height to copensate for margin, title, and footer.*/
92
+ }
93
+ .rgthree-dialog-container-content p {
94
+ font-size: calc(13rem / 16);
95
+ margin-top: 0;
96
+ }
97
+
98
+ .rgthree-dialog-container-content ul li p {
99
+ margin-bottom: 4px;
100
+ }
101
+
102
+ .rgthree-dialog-container-content ul li p + p {
103
+ margin-top: 0.5em;
104
+ }
105
+
106
+ .rgthree-dialog-container-content ul li ul {
107
+ margin-top: 0.5em;
108
+ margin-bottom: 1em;
109
+ }
110
+
111
+ .rgthree-dialog-container-content p code {
112
+ display: inline-block;
113
+ padding: 2px 4px;
114
+ margin: 0px 2px;
115
+ border: 1px solid rgba(255, 255, 255, 0.25);
116
+ border-radius: 3px;
117
+ background: rgba(255, 255, 255, 0.1);
118
+ }
119
+
120
+ .rgthree-dialog-container-footer {
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ }
125
+
126
+ body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
127
+ filter: blur(5px);
128
+ }
129
+
rgthree-comfy/src_web/common/css/dialog_lora_chooser.scss ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .rgthree-lora-chooser-dialog {
3
+ max-width: 100%;
4
+
5
+
6
+ .rgthree-dialog-container-title {
7
+ display: flex;
8
+ flex-direction: column;
9
+ }
10
+ .rgthree-dialog-container-title h2 {
11
+ display: flex;
12
+ width: 100%;
13
+ }
14
+ .rgthree-lora-chooser-search {
15
+ margin-left: auto;
16
+ border-radius: 50px;
17
+ width: 50%;
18
+ max-width: 170px;
19
+ padding: 2px 8px;
20
+ }
21
+
22
+ .rgthree-lora-chooser-header {
23
+ display: flex;
24
+ flex-direction: row;
25
+ }
26
+
27
+ .rgthree-lora-filters-container {
28
+ svg {width: 16px; height: 16px;}
29
+ }
30
+
31
+ .rgthree-dialog-container-content {
32
+ width: 80vw;
33
+ height: 80vh;
34
+ }
35
+
36
+ .rgthree-button-reset {
37
+ width: 32px;
38
+ height: 32px;
39
+ > svg {width: 100%; height: 100%;}
40
+
41
+ }
42
+
43
+ ul.rgthree-lora-chooser-list {
44
+ list-style: none;
45
+ margin: 0;
46
+ padding: 0;
47
+ position: relative;
48
+ display: flex;
49
+ flex-direction: row;
50
+ flex-wrap: wrap;
51
+ align-items: start;
52
+ justify-content: space-around;
53
+
54
+ > li {
55
+ position: relative;
56
+ flex: 0 0 auto;
57
+ width: 170px;
58
+ max-width: 100%;
59
+ margin: 8px 8px 16px;
60
+
61
+ label {
62
+ position: absolute;
63
+ display: block;
64
+ inset: 0;
65
+ z-index: 3;
66
+ cursor: pointer;
67
+ }
68
+ input[type="checkbox"] {
69
+ position: absolute;
70
+ right: 8px;
71
+ top: 8px;
72
+ margin: 0;
73
+ z-index: 2;
74
+ appearance: none;
75
+ background-color: #fff;
76
+ width: 48px;
77
+ height: 48px;
78
+ border-radius: 4px;
79
+ border: 1px solid rgba(120,120,120,1);
80
+ opacity: 0;
81
+ transition: opacity 0.15s ease-in-out;
82
+
83
+ &:checked {
84
+ opacity: 1;
85
+ background: #0060df;
86
+ &::before {
87
+ content: "";
88
+ display: block;
89
+ width: 100%;
90
+ height: 100%;
91
+ box-shadow: inset 100px 100px #fff;
92
+ clip-path: polygon(40.13% 68.39%, 23.05% 51.31%, 17.83% 48.26%, 12.61% 49.57%, 9.57% 53.04%, 8% 60%, 34.13% 85.87%, 39.82% 89.57%, 45.88% 86.73%, 90.66% 32.39%, 88.92% 26.1%, 83.03% 22.17%, 76.94% 22.62%)
93
+ }
94
+ }
95
+ }
96
+
97
+
98
+ figure {
99
+ position: relative;
100
+ display: block;
101
+ margin: 0 0 8px;
102
+ padding: 0;
103
+ border: 1px solid rgba(120, 120, 120, .8);
104
+ background: rgba(120, 120, 120, .5);
105
+ width: 100%;
106
+ padding-top: 120%;
107
+ transition: box-shadow 0.15s ease-in-out;
108
+ opacity: 0.75;
109
+ &::after {
110
+ content: '';
111
+ display: block;
112
+ position: absolute;
113
+ inset: 0;
114
+ }
115
+
116
+ &:empty {
117
+ &::before {
118
+ content: 'No image.';
119
+ color: rgba(200, 200, 200, .8);
120
+ position: absolute;
121
+ display: block;
122
+ inset: 0;
123
+ font-size: 1.2em;
124
+ text-align: center;
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ }
129
+ }
130
+
131
+ > img, > video {
132
+ position: absolute;
133
+ width: 100%;
134
+ height: 100%;
135
+ top: 0;
136
+ left: 0;
137
+ object-fit: cover;
138
+ }
139
+ }
140
+ div {
141
+ word-wrap: break-word;
142
+ font-size: 0.8rem;
143
+ opacity: 0.75;
144
+ }
145
+
146
+ &:hover figure::after{
147
+ box-shadow: 0px 2px 6px rgba(0,0,0,0.75);
148
+ }
149
+ :checked ~ figure::after {
150
+ box-shadow: 0 0 5px #fff, 0px 0px 15px rgba(49, 131, 255, 0.88), inset 0 0 3px #fff, inset 0px 0px 5px rgba(49, 131, 255, 0.88)
151
+ }
152
+
153
+ &:hover *,
154
+ &:hover input[type="checkbox"],
155
+ :checked ~ * {
156
+ opacity: 1
157
+ }
158
+
159
+ }
160
+ }
161
+ }
rgthree-comfy/src_web/common/css/dialog_model_info.scss ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .rgthree-info-dialog {
3
+
4
+ width: 90vw;
5
+ max-width: 960px;
6
+
7
+ .rgthree-info-area {
8
+ list-style: none;
9
+ padding: 0;
10
+ margin: 0;
11
+ display: flex;
12
+
13
+ > li {
14
+ display: inline-flex;
15
+ margin: 0;
16
+ vertical-align: top;
17
+
18
+ + li {
19
+ margin-left: 6px;
20
+ }
21
+ &:not(.-link) + li.-link {
22
+ margin-left: auto;
23
+ }
24
+
25
+ &.rgthree-info-tag > * {
26
+ min-height: 24px;
27
+ border-radius: 4px;
28
+ line-height: 1;
29
+ color: rgba(255,255,255,0.85);
30
+ background: rgb(69, 92, 85);;
31
+ font-size: 14px;
32
+ font-weight: bold;
33
+ text-decoration: none;
34
+ display: flex;
35
+ height: 1.6em;
36
+ padding-left: .5em;
37
+ padding-right: .5em;
38
+ padding-bottom: .1em;
39
+ align-content: center;
40
+ justify-content: center;
41
+ align-items: center;
42
+ box-shadow: inset 0px 0px 0 1px rgba(0, 0, 0, 0.5);
43
+
44
+ > svg {
45
+ width: 16px;
46
+ height: 16px;
47
+
48
+ &:last-child {
49
+ margin-left: .5em;
50
+ }
51
+ }
52
+
53
+
54
+ &[href] {
55
+ box-shadow: inset 0px 1px 0px rgba(255,255,255,0.25), inset 0px -1px 0px rgba(0,0,0,0.66);
56
+ }
57
+
58
+ &:empty {
59
+ display: none;
60
+ }
61
+ }
62
+
63
+ // &.-civitai > * {
64
+ // color: #ddd;
65
+ // background: #1b65aa;
66
+ // transition: all 0.15s ease-in-out;
67
+ // &:hover {
68
+ // color: #fff;
69
+ // border-color: #1971c2;
70
+ // background: #1971c2;
71
+ // }
72
+ // }
73
+ &.-type > * {
74
+ background: rgb(73, 54, 94);
75
+ color: rgb(228, 209, 248);
76
+ }
77
+
78
+ &.rgthree-info-menu {
79
+ margin-left: auto;
80
+
81
+ :not(#fakeid) & .rgthree-button {
82
+ margin: 0;
83
+ min-height: 24px;
84
+ padding: 0 12px;
85
+ }
86
+
87
+ svg {
88
+ width: 16px;
89
+ height: 16px;
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ .rgthree-info-table {
96
+ border-collapse: collapse;
97
+ margin: 16px 0px;
98
+ width: 100%;
99
+ font-size: 12px;
100
+
101
+ tr.editable button {
102
+ display: flex;
103
+ width: 28px;
104
+ height: 28px;
105
+ align-items: center;
106
+ justify-content: center;
107
+
108
+ svg + svg {display: none;}
109
+ }
110
+ tr.editable.-rgthree-editing button {
111
+ svg {display: none;}
112
+ svg + svg {display: inline-block;}
113
+ }
114
+
115
+ td {
116
+ position: relative;
117
+ border: 1px solid rgba(255,255,255,0.25);
118
+ padding: 0;
119
+ vertical-align: top;
120
+
121
+ &:first-child {
122
+ background: rgba(255,255,255,0.075);
123
+ width: 10px; // Small, so it doesn't adjust.
124
+ > *:first-child {
125
+ white-space: nowrap;
126
+ padding-right: 32px;
127
+ }
128
+
129
+ small {
130
+ display: block;
131
+ margin-top: 2px;
132
+ opacity: 0.75;
133
+
134
+ > [data-action] {
135
+ text-decoration: underline;
136
+ cursor: pointer;
137
+ &:hover {
138
+ text-decoration: none;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ a, a:hover, a:visited {
145
+ color: inherit;
146
+ }
147
+
148
+ svg {
149
+ width: 1.3333em;
150
+ height: 1.3333em;
151
+ vertical-align: -0.285em;
152
+
153
+ &.logo-civitai {
154
+ margin-right: 0.3333em;
155
+ }
156
+ }
157
+
158
+ > *:first-child {
159
+ display: block;
160
+ padding: 6px 10px;
161
+ }
162
+
163
+ > input, > textarea{
164
+ padding: 5px 10px;
165
+ border: 0;
166
+ box-shadow: inset 1px 1px 5px 0px rgba(0,0,0,0.5);
167
+ font: inherit;
168
+ appearance: none;
169
+ background: #fff;
170
+ color: #121212;
171
+ resize: vertical;
172
+
173
+ &:only-child {
174
+ width: 100%;
175
+ }
176
+ }
177
+
178
+ :not(#fakeid) & .rgthree-button[data-action="fetch-civitai"] {
179
+ font-size: inherit;
180
+ padding: 6px 16px;
181
+ margin: 2px;
182
+ }
183
+ }
184
+
185
+ tr[data-field-name="userNote"] td > span:first-child {
186
+ white-space: pre;
187
+ }
188
+
189
+ tr.rgthree-info-table-break-row td {
190
+ border: 0;
191
+ background: transparent;
192
+ padding: 12px 4px 4px;
193
+ font-size: 1.2em;
194
+
195
+ > small {
196
+ font-style: italic;
197
+ opacity: 0.66;
198
+ }
199
+
200
+ &:empty {
201
+ padding: 4px;
202
+ }
203
+ }
204
+
205
+ td .-help {
206
+ border: 1px solid currentColor;
207
+ position: absolute;
208
+ right: 5px;
209
+ top: 6px;
210
+ line-height: 1;
211
+ font-size: 11px;
212
+ width: 12px;
213
+ height: 12px;
214
+ border-radius: 8px;
215
+ display: flex;
216
+ align-content: center;
217
+ justify-content: center;
218
+ cursor: help;
219
+ &::before {
220
+ content: '?';
221
+ }
222
+
223
+ }
224
+
225
+ td > ul.rgthree-info-trained-words-list {
226
+ list-style: none;
227
+ padding: 2px 8px;
228
+ margin: 0;
229
+ display: flex;
230
+ flex-direction: row;
231
+ flex-wrap: wrap;
232
+ max-height: 15vh;
233
+ overflow: auto;
234
+
235
+ > li {
236
+ display: inline-flex;
237
+ margin: 2px;
238
+ vertical-align: top;
239
+ border-radius: 4px;
240
+ line-height: 1;
241
+ color: rgba(255,255,255,0.85);
242
+ background: rgb(73, 91, 106);
243
+ font-size: 1.2em;
244
+ font-weight: 600;
245
+ text-decoration: none;
246
+ display: flex;
247
+ height: 1.6em;
248
+ align-content: center;
249
+ justify-content: center;
250
+ align-items: center;
251
+ box-shadow: inset 0px 0px 0 1px rgba(0, 0, 0, 0.5);
252
+ cursor: pointer;
253
+ white-space: nowrap;
254
+ max-width: 183px;
255
+
256
+ &:hover {
257
+ background: rgb(68, 109, 142);
258
+ }
259
+
260
+ > svg {
261
+ width: auto;
262
+ height: 1.2em;
263
+ }
264
+
265
+ > span {
266
+ padding-left: .5em;
267
+ padding-right: .5em;
268
+ padding-bottom: .1em;
269
+ text-overflow: ellipsis;
270
+ overflow: hidden;
271
+ }
272
+
273
+ > small {
274
+ align-self: stretch;
275
+ display: flex;
276
+ align-items: center;
277
+ justify-content: center;
278
+ padding: 0 0.5em;
279
+ background: rgba(0,0,0,0.2);
280
+ }
281
+
282
+ &.-rgthree-is-selected {
283
+ background: rgb(42, 126, 193);
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ .rgthree-info-images {
290
+ list-style:none;
291
+ padding:0;
292
+ margin:0;
293
+ scroll-snap-type: x mandatory;
294
+ display:flex;
295
+ flex-direction:row;
296
+ overflow: auto;
297
+
298
+ > li {
299
+ scroll-snap-align: start;
300
+ max-width: 90%;
301
+ flex: 0 0 auto;
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: center;
305
+ flex-direction: column;
306
+ overflow: hidden;
307
+ padding: 0;
308
+ margin: 6px;
309
+ font-size: 0;
310
+ position: relative;
311
+
312
+ figure {
313
+ margin: 0;
314
+ position: static;
315
+
316
+ figcaption {
317
+ position: absolute;
318
+ left: 0;
319
+ width: 100%;
320
+ bottom: 0;
321
+ padding: 12px;
322
+ font-size: 12px;
323
+ background: rgba(0,0,0,0.85);
324
+ opacity: 0;
325
+ transform: translateY(50px);
326
+ transition: all 0.25s ease-in-out;
327
+
328
+ > span {
329
+ display: inline-block;
330
+ padding: 2px 4px;
331
+ margin: 2px;
332
+ border-radius: 2px;
333
+ border: 1px solid rgba(255,255,255,0.2);
334
+ word-break: break-word;
335
+
336
+ label {
337
+ display: inline;
338
+ padding: 0;
339
+ margin: 0;
340
+ opacity: 0.5;
341
+ pointer-events: none;
342
+ user-select: none;
343
+ }
344
+ a {
345
+ color: inherit;
346
+ text-decoration: underline;
347
+ &:hover {
348
+ text-decoration: none;
349
+ }
350
+
351
+ svg {
352
+ height: 10px;
353
+ margin-left: 4px;
354
+ fill: currentColor;
355
+ }
356
+ }
357
+ }
358
+ &:empty {
359
+ text-align: center;
360
+
361
+ &::before {
362
+ content: 'No data.';
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ &:hover figure figcaption {
369
+ opacity: 1;
370
+ transform: translateY(0px);
371
+ }
372
+
373
+ .rgthree-info-table {
374
+ width: calc(100% - 16px);
375
+ }
376
+ }
377
+ }
378
+
379
+ .rgthree-info-civitai-link {
380
+ margin: 8px;
381
+ color: #eee;
382
+
383
+ a, a:hover, a:visited {
384
+ color: inherit;
385
+ text-decoration: none;
386
+ }
387
+
388
+ > svg {
389
+ width: 16px;
390
+ height: 16px;
391
+ margin-right: 8px;
392
+ }
393
+ }
394
+ }
395
+
396
+
rgthree-comfy/src_web/common/css/menu.scss ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ .rgthree-menu {
4
+ list-style: none;
5
+ padding: 0;
6
+ margin: 0;
7
+ position: fixed;
8
+ z-index: 999999;
9
+ pointer-events: none;
10
+ opacity: 0;
11
+ transition: opacity 0.08s ease-in-out;
12
+
13
+ color: #dde;
14
+ background-color: #111;
15
+ font-size: 12px;
16
+ box-shadow: 0 0 10px black !important;
17
+
18
+ > li {
19
+ position: relative;
20
+ padding: 4px 6px;
21
+ z-index: 9999;
22
+ white-space: nowrap;
23
+
24
+ &[role="button"] {
25
+ background-color: var(--comfy-menu-bg) !important;
26
+ color: var(--input-text);
27
+ cursor: pointer;
28
+ &:hover {
29
+ filter: brightness(155%);
30
+ }
31
+ }
32
+ }
33
+
34
+ &[state^="measuring"] {
35
+ display: block;
36
+ opacity: 0;
37
+ }
38
+ &[state="open"] {
39
+ display: block;
40
+ opacity: 1;
41
+ pointer-events: all;
42
+ }
43
+ }
44
+
45
+
46
+ .rgthree-top-menu {
47
+ box-sizing: border-box;
48
+ white-space: nowrap;
49
+ background: var(--content-bg);
50
+ color: var(--content-fg);
51
+ display: flex;
52
+ flex-direction: column;
53
+ * {
54
+ box-sizing: inherit;
55
+ }
56
+
57
+ menu {
58
+ list-style: none;
59
+ padding: 0;
60
+ margin: 0;
61
+
62
+ > li:not(#fakeid) {
63
+ list-style: none;
64
+ padding: 0;
65
+ margin: 0;
66
+
67
+ > button {
68
+ cursor: pointer;
69
+ padding: 8px 12px 8px 8px;
70
+ width: 100%;
71
+ text-align: start;
72
+ display: flex;
73
+ flex-direction: row;
74
+ align-items: center;
75
+ justify-content: start;
76
+
77
+ &:hover {
78
+ background-color: var(--comfy-input-bg);
79
+ }
80
+
81
+ svg {
82
+ height: 16px;
83
+ width: auto;
84
+ margin-inline-end: 0.6em;
85
+
86
+ &.github-star {
87
+ fill: rgb(227, 179, 65);
88
+ }
89
+ }
90
+ }
91
+
92
+ &.rgthree-message {
93
+ // ComfyUI's code has strange behavior that that always puts the popupat to if its less than
94
+ // 30px... we'll force our message to be at least 32px tall so it won't do that unless it's
95
+ // actually on the bottom.
96
+ min-height: 32px;
97
+ > span {
98
+ padding: 8px 12px;
99
+ display: block;
100
+ width: 100%;
101
+ text-align: center;
102
+ font-style: italic;
103
+ font-size: 12px;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
rgthree-comfy/src_web/common/css/pages_base.scss ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ html, body {
3
+
4
+ }
5
+ html {
6
+ font-size: 100%;
7
+ overflow-y: scroll;
8
+ -webkit-text-size-adjust: 100%;
9
+ -ms-text-size-adjust: 100%;
10
+ box-sizing: border-box;
11
+ }
12
+ *, *:before, *:after {
13
+ box-sizing: inherit
14
+ }
15
+
16
+ :root {
17
+ --header-height: 56px;
18
+ --progress-height: 12px;
19
+ }
20
+
21
+ button {
22
+ all: unset;
23
+ }
24
+
25
+ .-bevel {
26
+ position: relative;
27
+ }
28
+ .-bevel::before {
29
+ content: '';
30
+ position: absolute;
31
+ left: 0;
32
+ top: 0;
33
+ width: 100%;
34
+ height: 100%;
35
+ border: 1px solid red;
36
+ border-color: rgba(255,255,255,0.15) rgba(255,255,255,0.15) rgba(0,0,0,0.5) rgba(0,0,0,0.5);
37
+ z-index: 5;
38
+ pointer-events: none;
39
+ }
40
+
41
+
42
+ body {
43
+ background: #202020;
44
+ font-family: Arial, sans-serif;
45
+ font-size: calc(16 * 0.0625rem);
46
+ font-weight: 400;
47
+ margin: 0;
48
+ padding-top: calc(var(--header-height) + var(--progress-height));
49
+ color: #ffffff;
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ justify-content: start;
54
+ }
55
+
56
+ .app-header {
57
+ height: var( --header-height);
58
+ padding: 0;
59
+ position: fixed;
60
+ z-index: 99;
61
+ top: 0;
62
+ left: 0;
63
+ width: 100%;
64
+ background: #353535;
65
+ display: flex;
66
+ flex-direction: row;
67
+ align-items: center;
68
+ justify-content: start;
69
+ }
rgthree-comfy/src_web/common/media/rgthree.svg ADDED
rgthree-comfy/src_web/common/media/svgs.ts ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createElement as $el } from "../utils_dom.js";
2
+
3
+ // Some svg repo : https://www.svgrepo.com/svg/326731/open-outline
4
+
5
+ export const logoRgthree = `<svg viewBox="0 0 256 256" fill="currentColor" class="rgthree-logo">
6
+ <path d="M88.503,158.997 L152.731,196.103 L152.738,196.092 L152.762,196.103 L152.769,196.106 L152.771,196.103 L183.922,142.084 L174.153,136.437 L148.611,180.676 L101.512,153.484 L132.193,30.415 L156.124,71.869 L165.896,66.225 L128.002,0.59 "></path>
7
+ <path d="M55.586,148.581l13.44,47.521l0.014,0.051l0.168-0.051l10.689-3.022l-6.589-23.313l45.609,26.335l0.087,0.051l0.027-0.051 l5.617-9.718l-42.648-24.622l35.771-143.45L33.232,164.729l9.77,5.645L55.586,148.581z M87.394,93.484l-16.708,67.018l-5.018-17.747 l-8.028,2.27L87.394,93.484z"></path>
8
+ <path d="M189.85,107.717 L137.892,137.718 L143.532,147.49 L185.723,123.133 L231.109,201.746 L24.895,201.746 L37.363,180.146 L27.592,174.505 L5.347,213.03 L250.653,213.03 "></path>
9
+ <path d="M5.347,247.299v8.111h245.307v-8.111l-41.94-0.003c-1.336,0-2.404-1.065-2.441-2.396v-12.14 c0.037-1.315,1.089-2.368,2.41-2.385h41.972v-8.11H5.347v8.11h41.951c1.338,0.017,2.427,1.104,2.427,2.449v12.01 c0,1.365-1.105,2.462-2.457,2.462L5.347,247.299z M139.438,247.296c-1.334,0-2.406-1.065-2.439-2.396v-12.14 c0.033-1.315,1.085-2.368,2.41-2.385h46.415c1.335,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.459,2.462H139.438z M70.193,247.296c-1.339,0-2.408-1.065-2.441-2.396v-12.14c0.033-1.315,1.086-2.368,2.407-2.385h46.418 c1.336,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.458,2.462H70.193z"></path>
10
+ </svg>`;
11
+
12
+ export const github = `<svg viewBox="0 0 16 16" fill="currentColor" class="github-logo">
13
+ <path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
14
+ </svg>`;
15
+
16
+ export const iconStarFilled = `<svg viewBox="0 0 16 16" fill="currentColor" class="github-star">
17
+ <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path>
18
+ </svg>`;
19
+
20
+ export const iconReplace = `<svg viewBox="0 0 52 52" fill="currentColor">
21
+ <path d="M20,37.5c0-0.8-0.7-1.5-1.5-1.5h-15C2.7,36,2,36.7,2,37.5v11C2,49.3,2.7,50,3.5,50h15c0.8,0,1.5-0.7,1.5-1.5 V37.5z"/>
22
+ <path d="M8.1,22H3.2c-1,0-1.5,0.9-0.9,1.4l8,8.3c0.4,0.3,1,0.3,1.4,0l8-8.3c0.6-0.6,0.1-1.4-0.9-1.4h-4.7 c0-5,4.9-10,9.9-10V6C15,6,8.1,13,8.1,22z"/>
23
+ <path d="M41.8,20.3c-0.4-0.3-1-0.3-1.4,0l-8,8.3c-0.6,0.6-0.1,1.4,0.9,1.4h4.8c0,6-4.1,10-10.1,10v6 c9,0,16.1-7,16.1-16H49c1,0,1.5-0.9,0.9-1.4L41.8,20.3z"/>
24
+ <path d="M50,3.5C50,2.7,49.3,2,48.5,2h-15C32.7,2,32,2.7,32,3.5v11c0,0.8,0.7,1.5,1.5,1.5h15c0.8,0,1.5-0.7,1.5-1.5 V3.5z"/>
25
+ </svg>`;
26
+
27
+ export const iconNode = `<svg viewBox="0 -0.5 25 25" fill="none">
28
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 19H9.5C7.29086 19 5.5 17.2091 5.5 15V9C5.5 6.79086 7.29086 5 9.5 5H15.5C17.7091 5 19.5 6.79086 19.5 9V15C19.5 17.2091 17.7091 19 15.5 19Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
29
+ <path d="M19.5 9.75C19.9142 9.75 20.25 9.41421 20.25 9C20.25 8.58579 19.9142 8.25 19.5 8.25V9.75ZM5.5 8.25C5.08579 8.25 4.75 8.58579 4.75 9C4.75 9.41421 5.08579 9.75 5.5 9.75V8.25ZM11.5 14.25C11.0858 14.25 10.75 14.5858 10.75 15C10.75 15.4142 11.0858 15.75 11.5 15.75V14.25ZM13.5 15.75C13.9142 15.75 14.25 15.4142 14.25 15C14.25 14.5858 13.9142 14.25 13.5 14.25V15.75ZM19.5 8.25H5.5V9.75H19.5V8.25ZM11.5 15.75H13.5V14.25H11.5V15.75Z" fill="currentColor" />
30
+ </svg>`;
31
+
32
+ export const iconGear = `<svg viewBox="0 0 24 24" fill="currentColor">
33
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.7848 0.449982C13.8239 0.449982 14.7167 1.16546 14.9122 2.15495L14.9991 2.59495C15.3408 4.32442 17.1859 5.35722 18.9016 4.7794L19.3383 4.63233C20.3199 4.30175 21.4054 4.69358 21.9249 5.56605L22.7097 6.88386C23.2293 7.75636 23.0365 8.86366 22.2504 9.52253L21.9008 9.81555C20.5267 10.9672 20.5267 13.0328 21.9008 14.1844L22.2504 14.4774C23.0365 15.1363 23.2293 16.2436 22.7097 17.1161L21.925 18.4339C21.4054 19.3064 20.3199 19.6982 19.3382 19.3676L18.9017 19.2205C17.1859 18.6426 15.3408 19.6754 14.9991 21.405L14.9122 21.845C14.7167 22.8345 13.8239 23.55 12.7848 23.55H11.2152C10.1761 23.55 9.28331 22.8345 9.08781 21.8451L9.00082 21.4048C8.65909 19.6754 6.81395 18.6426 5.09822 19.2205L4.66179 19.3675C3.68016 19.6982 2.59465 19.3063 2.07505 18.4338L1.2903 17.1161C0.770719 16.2436 0.963446 15.1363 1.74956 14.4774L2.09922 14.1844C3.47324 13.0327 3.47324 10.9672 2.09922 9.8156L1.74956 9.52254C0.963446 8.86366 0.77072 7.75638 1.2903 6.8839L2.07508 5.56608C2.59466 4.69359 3.68014 4.30176 4.66176 4.63236L5.09831 4.77939C6.81401 5.35722 8.65909 4.32449 9.00082 2.59506L9.0878 2.15487C9.28331 1.16542 10.176 0.449982 11.2152 0.449982H12.7848ZM12 15.3C13.8225 15.3 15.3 13.8225 15.3 12C15.3 10.1774 13.8225 8.69998 12 8.69998C10.1774 8.69998 8.69997 10.1774 8.69997 12C8.69997 13.8225 10.1774 15.3 12 15.3Z" />
34
+ </svg>`;
35
+
36
+ export const checkmark = `<svg viewBox="0 0 32 32" fill="currentColor" class="icon-checkmark">
37
+ <g transform="translate(-518.000000, -1039.000000)">
38
+ <path d="M548.783,1040.2 C547.188,1038.57 544.603,1038.57 543.008,1040.2 L528.569,1054.92 L524.96,1051.24 C523.365,1049.62 520.779,1049.62 519.185,1051.24 C517.59,1052.87 517.59,1055.51 519.185,1057.13 L525.682,1063.76 C527.277,1065.39 529.862,1065.39 531.457,1063.76 L548.783,1046.09 C550.378,1044.46 550.378,1041.82 548.783,1040.2"></path>
39
+ </g>
40
+ </svg>`;
41
+
42
+ export const logoCivitai = `<svg viewBox="0 0 178 178" class="logo-civitai">
43
+ <defs>
44
+ <linearGradient id="bgblue" gradientUnits="userSpaceOnUse" x1="89.3" y1="-665.5" x2="89.3" y2="-841.1" gradientTransform="matrix(1 0 0 -1 0 -664)">
45
+ <stop offset="0" style="stop-color:#1284F7"/>
46
+ <stop offset="1" style="stop-color:#0A20C9"/>
47
+ </linearGradient>
48
+ </defs>
49
+ <path fill="#000" d="M13.3,45.4v87.7l76,43.9l76-43.9V45.4l-76-43.9L13.3,45.4z"/>
50
+ <path style="fill:url(#bgblue);" d="M89.3,29.2l52,30v60l-52,30l-52-30v-60 L89.3,29.2 M89.3,1.5l-76,43.9v87.8l76,43.9l76-43.9V45.4L89.3,1.5z" />
51
+ <path fill="#FFF" d="M104.1,97.2l-14.9,8.5l-14.9-8.5v-17l14.9-8.5l14.9,8.5h18.2V69.7l-33-19l-33,19v38.1l33,19l33-19V97.2H104.1z" />
52
+ </svg>`;
53
+
54
+ export const iconOutLink = `<svg viewBox="0 0 32 32">
55
+ <path d="M 18 5 L 18 7 L 23.5625 7 L 11.28125 19.28125 L 12.71875 20.71875 L 25 8.4375 L 25 14 L 27 14 L 27 5 Z M 5 9 L 5 27 L 23 27 L 23 14 L 21 16 L 21 25 L 7 25 L 7 11 L 16 11 L 18 9 Z"></path>
56
+ </svg>`;
57
+
58
+ export const link = `<svg viewBox="0 0 640 512">
59
+ <path d="M598.6 41.41C570.1 13.8 534.8 0 498.6 0s-72.36 13.8-99.96 41.41l-43.36 43.36c15.11 8.012 29.47 17.58 41.91 30.02c3.146 3.146 5.898 6.518 8.742 9.838l37.96-37.96C458.5 72.05 477.1 64 498.6 64c20.67 0 40.1 8.047 54.71 22.66c14.61 14.61 22.66 34.04 22.66 54.71s-8.049 40.1-22.66 54.71l-133.3 133.3C405.5 343.1 386 352 365.4 352s-40.1-8.048-54.71-22.66C296 314.7 287.1 295.3 287.1 274.6s8.047-40.1 22.66-54.71L314.2 216.4C312.1 212.5 309.9 208.5 306.7 205.3C298.1 196.7 286.8 192 274.6 192c-11.93 0-23.1 4.664-31.61 12.97c-30.71 53.96-23.63 123.6 22.39 169.6C293 402.2 329.2 416 365.4 416c36.18 0 72.36-13.8 99.96-41.41L598.6 241.3c28.45-28.45 42.24-66.01 41.37-103.3C639.1 102.1 625.4 68.16 598.6 41.41zM234 387.4L196.1 425.3C181.5 439.1 162 448 141.4 448c-20.67 0-40.1-8.047-54.71-22.66c-14.61-14.61-22.66-34.04-22.66-54.71s8.049-40.1 22.66-54.71l133.3-133.3C234.5 168 253.1 160 274.6 160s40.1 8.048 54.71 22.66c14.62 14.61 22.66 34.04 22.66 54.71s-8.047 40.1-22.66 54.71L325.8 295.6c2.094 3.939 4.219 7.895 7.465 11.15C341.9 315.3 353.3 320 365.4 320c11.93 0 23.1-4.664 31.61-12.97c30.71-53.96 23.63-123.6-22.39-169.6C346.1 109.8 310.8 96 274.6 96C238.4 96 202.3 109.8 174.7 137.4L41.41 270.7c-27.6 27.6-41.41 63.78-41.41 99.96c-.0001 36.18 13.8 72.36 41.41 99.97C69.01 498.2 105.2 512 141.4 512c36.18 0 72.36-13.8 99.96-41.41l43.36-43.36c-15.11-8.012-29.47-17.58-41.91-30.02C239.6 394.1 236.9 390.7 234 387.4z"/>
60
+ </svg>`;
61
+
62
+ export const pencil = `<svg viewBox="0 0 24 24">
63
+ <path d="M 16.9375 1.0625 L 3.875 14.125 L 1.0742188 22.925781 L 9.875 20.125 L 22.9375 7.0625 C 22.9375 7.0625 22.8375 4.9615 20.9375 3.0625 C 19.0375 1.1625 16.9375 1.0625 16.9375 1.0625 z M 17.3125 2.6875 C 18.3845 2.8915 19.237984 3.3456094 19.896484 4.0214844 C 20.554984 4.6973594 21.0185 5.595 21.3125 6.6875 L 19.5 8.5 L 15.5 4.5 L 16.9375 3.0625 L 17.3125 2.6875 z M 4.9785156 15.126953 C 4.990338 15.129931 6.1809555 15.430955 7.375 16.625 C 8.675 17.825 8.875 18.925781 8.875 18.925781 L 8.9179688 18.976562 L 5.3691406 20.119141 L 3.8730469 18.623047 L 4.9785156 15.126953 z"/>
64
+ </svg>`;
65
+
66
+ export const dotdotdot = `<svg viewBox="0 0 24 24" fill="currentColor">
67
+ <circle cy="12" r="3" cx="3"></circle>
68
+ <circle cy="12" r="3" cx="12"></circle>
69
+ <circle cx="21" cy="12" r="3"></circle>
70
+ </svg>`;
71
+
72
+ export const models = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
73
+ <path d="M4 4h6v6h-6z"></path>
74
+ <path d="M14 4h6v6h-6z"></path>
75
+ <path d="M4 14h6v6h-6z"></path>
76
+ <path d="M17 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
77
+ </svg>`;
78
+
79
+ /** https://www.svgrepo.com/svg/402308/pencil */
80
+ export const pencilColored = `<svg viewBox="0 0 64 64">
81
+ <path fill="#ffce31" d="M7.934 41.132L39.828 9.246l14.918 14.922l-31.895 31.886z"></path>
82
+ <path d="M61.3 4.6l-1.9-1.9C55.8-.9 50-.9 46.3 2.7l-6.5 6.5l15 15l6.5-6.5c3.6-3.6 3.6-9.5 0-13.1" fill="#ed4c5c"></path>
83
+ <path fill="#93a2aa" d="M35.782 13.31l4.1-4.102l14.92 14.92l-4.1 4.101z"></path>
84
+ <path fill="#c7d3d8" d="M37.338 14.865l4.1-4.101l11.739 11.738l-4.102 4.1z"></path>
85
+ <path fill="#fed0ac" d="M7.9 41.1l-6.5 17l4.5 4.5l17-6.5z"/>
86
+ <path d="M.3 61.1c-.9 2.4.3 3.5 2.7 2.6l8.2-3.1l-7.7-7.7l-3.2 8.2" fill="#333"></path>
87
+ <path fill="#ffdf85" d="M7.89 41.175l27.86-27.86l4.95 4.95l-27.86 27.86z"/>
88
+ <path fill="#ff8736" d="M17.904 51.142l27.86-27.86l4.95 4.95l-27.86 27.86z"></path>
89
+ </svg>`;
90
+
91
+ /** https://www.svgrepo.com/svg/395640/save */
92
+ export const diskColored = `<svg viewBox="-0.01 -0.008 100.016 100.016">
93
+ <path fill="#26f" fill_="#23475F" d="M88.555-.008H83v.016a2 2 0 0 1-2 2H19a2 2 0 0 1-2-2v-.016H4a4 4 0 0 0-4 4v92.016a4 4 0 0 0 4 4h92a4 4 0 0 0 4-4V11.517c.049-.089-11.436-11.454-11.445-11.525z"/>
94
+ <path fill="#04d" fill_="#1C3C50" d="M81.04 53.008H18.96a2 2 0 0 0-2 2v45h66.08v-45c0-1.106-.895-2-2-2zm-61.957-10h61.834a2 2 0 0 0 2-2V.547A1.993 1.993 0 0 1 81 2.007H19c-.916 0-1.681-.62-1.917-1.46v40.46a2 2 0 0 0 2 2.001z"/>
95
+ <path fill="#EBF0F1" d="M22 55.977h56a2 2 0 0 1 2 2v37.031a2 2 0 0 1-2 2H22c-1.104 0-2-.396-2-1.5V57.977a2 2 0 0 1 2-2z"/>
96
+ <path fill="#BCC4C8" d="M25 77.008h50v1H25v-1zm0 10h50v1H25v-1z"/>
97
+ <path fill="#1C3C50" d="M7 84.008h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm83 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2z"/>
98
+ <path fill="#BCC4C8" d="M37 1.981v36.026a2 2 0 0 0 2 2h39a2 2 0 0 0 2-2V1.981c0 .007-42.982.007-43 0zm37 29.027a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2V10.981a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v20.027z"/>
99
+ <path fill="#FF9D00" d="M78 55.977H22a2 2 0 0 0-2 2v10.031h60V57.977a2 2 0 0 0-2-2z"/>
100
+ </svg>`;
101
+
102
+ /** https://www.svgrepo.com/svg/229838/folder */
103
+ export const folderColored = `<svg viewBox="0 0 501.379 501.379">
104
+ <path style="fill:#EF9F2C;" d="M406.423,93.889H205.889c-17.067,0-30.933-13.867-30.933-30.933s-13.867-30.933-30.933-30.933H30.956
105
+ c-17.067,0-30.933,13.867-30.933,30.933v375.467c0,17.067,13.867,30.933,30.933,30.933h375.467
106
+ c17.067,0,30.933-13.867,30.933-30.933v-313.6C436.289,107.756,422.423,93.889,406.423,93.889z"/>
107
+ <path style="fill:#FEC656;" d="M470.423,157.889H97.089c-13.867,0-26.667,9.6-29.867,22.4l-66.133,249.6
108
+ c-5.333,19.2,9.6,38.4,29.867,38.4h373.333c13.867,0,26.667-9.6,29.867-22.4l66.133-248.533
109
+ C505.623,177.089,490.689,157.889,470.423,157.889z"/>
110
+ </svg>`;
111
+
112
+ export const modelsColored = `<svg viewBox="0 0 24 24">
113
+ <path fill="#aa3366" d="M0 0h10v10h-10z"></path>
114
+ <path d="M14 0h10v10h-10z" fill="#3366aa"></path>
115
+ <path d="M0 14h10v10h-10z" fill="#66aa33"></path>
116
+ <path fill="#dd9922" d="M19 19m-5 0 a5 5 0 1 0 10 0 a5 5 0 1 0 -10 0"></path>
117
+ </svg>`;
118
+
119
+ export const legoBlocksColored = `<svg viewBox="0 0 512 512">
120
+ <g>
121
+ <rect x="57.67" style="fill:#00BAB9;" width="101.275" height="78.769"/>
122
+ <rect x="205.363" style="fill:#00BAB9;" width="101.275" height="78.769"/>
123
+ <rect x="353.055" style="fill:#00BAB9;" width="101.275" height="78.769"/>
124
+ </g>
125
+ <polygon style="fill:#B8DE6F;" points="478.242,289.758 478.242,512 33.758,512 33.758,289.758 256,267.253 "/>
126
+ <polygon style="fill:#41D4D3;" points="478.242,67.516 478.242,289.758 33.758,289.758 33.758,67.516 57.67,67.516 158.945,67.516
127
+ 205.363,67.516 306.637,67.516 353.055,67.516 454.33,67.516 "/>
128
+ <g>
129
+ <circle style="fill:#00BAB9;" cx="402.286" cy="143.473" r="8.44"/>
130
+ <circle style="fill:#00BAB9;" cx="368.527" cy="177.231" r="8.44"/>
131
+ </g>
132
+ <circle style="fill:#7BD288;" cx="109.714" cy="436.044" r="8.44"/>
133
+ </svg>`;
134
+
135
+ export const legoBlockColored = `<svg viewBox="0 0 256 256">
136
+ <style>
137
+ .s0 { fill: #ff0000 }
138
+ .s1 { fill: #c30000 }
139
+ .s2 { fill: #800000 }
140
+ .s3 { fill: #cc0000 }
141
+ .s4 { fill: #e00000 }
142
+ </style>
143
+ <g id="Folder 2">
144
+ <path id="Shape 1 copy 2" class="s0" d="m128 61l116 45-116 139-116-139z"/>
145
+ <path id="Shape 1" class="s1" d="m12 106l116 45v95l-116-45z"/>
146
+ <path id="Shape 1 copy" class="s2" d="m244 106l-116 45v95l116-45z"/>
147
+ <g id="Folder 1">
148
+ <path id="Shape 2" class="s3" d="m102 111.2c0-6.1 11.4-9.9 25.5-9.9 14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9-14.1 0-25.5-4.8-25.5-10.9 0-3.3 0-13.3 0-16.6z"/>
149
+ <path id="Shape 2 copy 4" class="s1" d="m102 111.2c0-6.1 11.4-9.9 25.5-9.9 14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9-14.1 0-25.5-4.8-25.5-10.9 0-3.3 0-13.3 0-16.6z"/>
150
+ <path id="Shape 2 copy 2" class="s2" d="m127.5 101.3c14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9 0-13.1 0-25.7 0-37.4z"/>
151
+ <path id="Shape 2 copy" class="s0" d="m127.5 118.8c-12.2 0-22-3.4-22-7.6 0-4.2 9.8-7.7 22-7.7 12.2 0 22 3.5 22 7.7 0 4.2-9.8 7.6-22 7.6zm0 0c-12.2 0-22-3.4-22-7.6 0-4.2 9.8-7.7 22-7.7 12.2 0 22 3.5 22 7.7 0 4.2-9.8 7.6-22 7.6z"/>
152
+ </g>
153
+ <g id="Folder 1 copy">
154
+ <path id="Shape 2" class="s4" d="m103 67.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
155
+ <path id="Shape 2 copy 4" class="s1" d="m103 67.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
156
+ <path id="Shape 2 copy 2" class="s2" d="m127.5 58c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
157
+ <path id="Shape 2 copy" class="s0" d="m127.5 74.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
158
+ </g>
159
+ <g id="Folder 1 copy 2">
160
+ <path id="Shape 2" class="s4" d="m161 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
161
+ <path id="Shape 2 copy 4" class="s1" d="m161 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
162
+ <path id="Shape 2 copy 2" class="s2" d="m185.5 80c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
163
+ <path id="Shape 2 copy" class="s0" d="m185.5 96.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
164
+ </g>
165
+ <g id="Folder 1 copy 3">
166
+ <path id="Shape 2" class="s4" d="m45 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
167
+ <path id="Shape 2 copy 4" class="s1" d="m45 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
168
+ <path id="Shape 2 copy 2" class="s2" d="m69.5 80c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
169
+ <path id="Shape 2 copy" class="s0" d="m69.5 96.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
170
+ </g>
171
+ </g>
172
+ </svg>`;
173
+
174
+ export const gearColored = `<svg viewBox="0 0 128 128" preserveAspectRatio="xMidYMid meet">
175
+ <path d="M124 71.85v-15.7c0-.59-.45-1.09-1.03-1.15l-17.83-1.89c-.47-.05-.85-.38-.98-.83c-.86-2.95-2.03-5.76-3.48-8.39c-.23-.41-.19-.92.11-1.28l11.28-13.94c.37-.46.34-1.13-.08-1.54l-11.1-11.1a1.15 1.15 0 0 0-1.54-.08L85.39 27.22c-.37.3-.87.33-1.28.11a41.796 41.796 0 0 0-8.39-3.48c-.45-.13-.78-.51-.83-.98L73 5.03C72.94 4.45 72.44 4 71.85 4h-15.7c-.59 0-1.09.45-1.15 1.03l-1.89 17.83c-.05.47-.38.85-.83.98c-2.95.86-5.76 2.03-8.39 3.48c-.41.23-.92.19-1.28-.11L28.67 15.94a1.15 1.15 0 0 0-1.54.08l-11.1 11.1a1.15 1.15 0 0 0-.08 1.54L27.23 42.6c.3.37.33.87.11 1.28a41.796 41.796 0 0 0-3.48 8.39c-.13.45-.51.78-.98.83L5.03 55c-.58.06-1.03.56-1.03 1.15v15.7c0 .59.45 1.09 1.03 1.15l17.83 1.89c.47.05.85.38.98.83c.86 2.95 2.03 5.76 3.48 8.39c.23.41.19.92-.11 1.28L15.94 99.33c-.37.46-.34 1.13.08 1.54l11.1 11.1c.42.42 1.08.45 1.54.08l13.94-11.28c.37-.3.87-.33 1.28-.11c2.64 1.45 5.45 2.62 8.39 3.48c.45.13.78.51.83.98l1.9 17.85c.06.59.56 1.03 1.15 1.03h15.7c.59 0 1.09-.45 1.15-1.03l1.89-17.83c.05-.47.38-.85.83-.98c2.95-.86 5.76-2.03 8.39-3.48c.41-.23.92-.19 1.28.11l13.94 11.28c.46.37 1.13.34 1.54-.08l11.1-11.1c.42-.42.45-1.08.08-1.54l-11.28-13.94c-.3-.37-.33-.87-.11-1.28c1.45-2.64 2.62-5.45 3.48-8.39c.13-.45.51-.78.98-.83L122.97 73c.58-.06 1.03-.56 1.03-1.15zm-60 3.43c-6.23 0-11.28-5.05-11.28-11.28S57.77 52.72 64 52.72S75.28 57.77 75.28 64S70.23 75.28 64 75.28z" fill="#82aec0"></path>
176
+ <path d="M80.56 49.48c3.67 4.18 5.78 9.77 5.43 15.85c-.65 11.16-9.83 20.19-21 20.68c-4.75.21-9.18-1.09-12.86-3.45c-.28-.18-.58.2-.34.44a22.412 22.412 0 0 0 17.85 6.67c10.78-.85 19.56-9.5 20.55-20.27c.77-8.36-3.06-15.87-9.23-20.33c-.29-.2-.62.15-.4.41z" fill="#2f7889"></path>
177
+ <path d="M43.87 65.32c-.67-13.15 7.83-22.79 20.01-22.79c.65 0 1.68 0 2.48.92c1.01 1.18 1.1 2.6 0 3.77c-.81.86-1.95.92-2.53 1c-12.3 1.59-15.18 9.35-15.83 16.77c-.03.33.06 2.35-1.71 2.56c-2.15.25-2.41-1.91-2.42-2.23z" fill="#b9e4ea"></path>
178
+ <path d="M25.24 65.87c-.01-22.03 15.9-40.19 38.13-41.05c.68-.03 2.45 0 3.55.99c1.01.91 1.38 2.51.79 3.82c-.95 2.11-2.85 2.07-3.36 2.09c-18.51.66-34.18 15.73-34.19 33.95c0 .29-.05.58-.15.84l-.1.25c-.76 1.98-3.52 2.09-4.43.18c-.15-.34-.24-.7-.24-1.07z" fill="#94d1e0"></path>
179
+ </svg>`;
180
+
181
+ export function $svg(markup: string, attrs: { [key: string]: string }) {
182
+ if (!markup.match(/^\s*<svg/)) {
183
+ throw new Error("Cannot call $svg with non-svg markup.");
184
+ }
185
+ return $el(markup, attrs || {});
186
+ }
rgthree-comfy/src_web/scripts_comfy/ui/components/button.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ComfyApp } from "typings/comfy.js";
2
+
3
+ type ComfyButtonProps = {
4
+ icon?: string;
5
+ overIcon?: string;
6
+ iconSize?: number;
7
+ content?: string | HTMLElement;
8
+ tooltip?: string;
9
+ enabled?: boolean;
10
+ action?: (e: Event, btn: ComfyButton) => void;
11
+ classList?: string;
12
+ visibilitySetting?: { id: string, showValue: any };
13
+ app?: ComfyApp;
14
+ }
15
+
16
+ export declare class ComfyButton {
17
+ element: HTMLElement;
18
+ iconElement: HTMLElement;
19
+ contentElement: HTMLElement;
20
+ constructor(props: ComfyButtonProps);
21
+ updateIcon(): void;
22
+ withPopup(popup: any, mode: "click"|"hover"): this;
23
+ };
rgthree-comfy/src_web/scripts_comfy/ui/components/buttonGroup.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import type {ComfyButton} from "scripts/ui/components/button.js";
2
+
3
+ export declare class ComfyButtonGroup {
4
+ element: HTMLElement;
5
+ constructor(...buttons: Array<ComfyButton|HTMLDivElement>);
6
+ insert(button: ComfyButton, index: number): void;
7
+ append(button: ComfyButton): void;
8
+ remove(indexOrButton: ComfyButton|number): ComfyButton|HTMLElement|void;
9
+ update(): void;
10
+ };
rgthree-comfy/src_web/scripts_comfy/ui/components/popup.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ type ComfyPopupProps = {
2
+ target: HTMLElement;
3
+ container?: HTMLElement;
4
+ classList?: string;
5
+ ignoreTarget?: boolean,
6
+ closeOnEscape?: boolean,
7
+ position?: "absolute" | "relative",
8
+ horizontal?: "left" | "right"
9
+ }
10
+
11
+ export declare class ComfyPopup extends EventTarget {
12
+ element: HTMLDivElement;
13
+ constructor(props: ComfyPopupProps, ...children: HTMLElement[]);
14
+ toggle(): void;
15
+ update(): void;
16
+ };
rgthree-comfy/web/comfyui/any_switch.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { IoDirection, addConnectionLayoutSupport, followConnectionUntilType } from "./utils.js";
3
+ import { RgthreeBaseServerNode } from "./base_node.js";
4
+ import { NodeTypesString } from "./constants.js";
5
+ import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
6
+ import { debounce } from "../../rgthree/common/shared_utils.js";
7
+ class RgthreeAnySwitch extends RgthreeBaseServerNode {
8
+ constructor(title = RgthreeAnySwitch.title) {
9
+ super(title);
10
+ this.stabilizeBound = this.stabilize.bind(this);
11
+ this.nodeType = null;
12
+ this.addAnyInput(5);
13
+ }
14
+ onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
15
+ var _a;
16
+ (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, linkInfo, ioSlot);
17
+ this.scheduleStabilize();
18
+ }
19
+ onConnectionsChainChange() {
20
+ this.scheduleStabilize();
21
+ }
22
+ scheduleStabilize(ms = 64) {
23
+ return debounce(this.stabilizeBound, ms);
24
+ }
25
+ addAnyInput(num = 1) {
26
+ for (let i = 0; i < num; i++) {
27
+ this.addInput(`any_${String(this.inputs.length + 1).padStart(2, "0")}`, (this.nodeType || "*"));
28
+ }
29
+ }
30
+ stabilize() {
31
+ removeUnusedInputsFromEnd(this, 4);
32
+ this.addAnyInput();
33
+ let connectedType = followConnectionUntilType(this, IoDirection.INPUT, undefined, true);
34
+ if (!connectedType) {
35
+ connectedType = followConnectionUntilType(this, IoDirection.OUTPUT, undefined, true);
36
+ }
37
+ this.nodeType = (connectedType === null || connectedType === void 0 ? void 0 : connectedType.type) || "*";
38
+ for (const input of this.inputs) {
39
+ input.type = this.nodeType;
40
+ }
41
+ for (const output of this.outputs) {
42
+ output.type = this.nodeType;
43
+ output.label =
44
+ output.type === "RGTHREE_CONTEXT"
45
+ ? "CONTEXT"
46
+ : Array.isArray(this.nodeType) || this.nodeType.includes(",")
47
+ ? (connectedType === null || connectedType === void 0 ? void 0 : connectedType.label) || String(this.nodeType)
48
+ : String(this.nodeType);
49
+ }
50
+ }
51
+ static setUp(comfyClass, nodeData) {
52
+ RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeAnySwitch);
53
+ addConnectionLayoutSupport(RgthreeAnySwitch, app, [
54
+ ["Left", "Right"],
55
+ ["Right", "Left"],
56
+ ]);
57
+ }
58
+ }
59
+ RgthreeAnySwitch.title = NodeTypesString.ANY_SWITCH;
60
+ RgthreeAnySwitch.type = NodeTypesString.ANY_SWITCH;
61
+ RgthreeAnySwitch.comfyClass = NodeTypesString.ANY_SWITCH;
62
+ app.registerExtension({
63
+ name: "rgthree.AnySwitch",
64
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
65
+ if (nodeData.name === "Any Switch (rgthree)") {
66
+ RgthreeAnySwitch.setUp(nodeType, nodeData);
67
+ }
68
+ },
69
+ });
rgthree-comfy/web/comfyui/base_any_input_connected_node.js ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { RgthreeBaseVirtualNode } from "./base_node.js";
3
+ import { rgthree } from "./rgthree.js";
4
+ import { PassThroughFollowing, addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js";
5
+ export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode {
6
+ constructor(title = BaseAnyInputConnectedNode.title) {
7
+ super(title);
8
+ this.isVirtualNode = true;
9
+ this.inputsPassThroughFollowing = PassThroughFollowing.NONE;
10
+ this.debouncerTempWidth = 0;
11
+ this.schedulePromise = null;
12
+ }
13
+ onConstructed() {
14
+ this.addInput("", "*");
15
+ return super.onConstructed();
16
+ }
17
+ clone() {
18
+ const cloned = super.clone();
19
+ if (!rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes) {
20
+ while (cloned.inputs.length > 1) {
21
+ cloned.removeInput(cloned.inputs.length - 1);
22
+ }
23
+ if (cloned.inputs[0]) {
24
+ cloned.inputs[0].label = "";
25
+ }
26
+ }
27
+ return cloned;
28
+ }
29
+ scheduleStabilizeWidgets(ms = 100) {
30
+ if (!this.schedulePromise) {
31
+ this.schedulePromise = new Promise((resolve) => {
32
+ setTimeout(() => {
33
+ this.schedulePromise = null;
34
+ this.doStablization();
35
+ resolve();
36
+ }, ms);
37
+ });
38
+ }
39
+ return this.schedulePromise;
40
+ }
41
+ stabilizeInputsOutputs() {
42
+ var _a;
43
+ let changed = false;
44
+ const hasEmptyInput = !((_a = this.inputs[this.inputs.length - 1]) === null || _a === void 0 ? void 0 : _a.link);
45
+ if (!hasEmptyInput) {
46
+ this.addInput("", "*");
47
+ changed = true;
48
+ }
49
+ for (let index = this.inputs.length - 2; index >= 0; index--) {
50
+ const input = this.inputs[index];
51
+ if (!input.link) {
52
+ this.removeInput(index);
53
+ changed = true;
54
+ }
55
+ else {
56
+ const node = getConnectedInputNodesAndFilterPassThroughs(this, this, index, this.inputsPassThroughFollowing)[0];
57
+ const newName = (node === null || node === void 0 ? void 0 : node.title) || "";
58
+ if (input.name !== newName) {
59
+ input.name = (node === null || node === void 0 ? void 0 : node.title) || "";
60
+ changed = true;
61
+ }
62
+ }
63
+ }
64
+ return changed;
65
+ }
66
+ doStablization() {
67
+ if (!this.graph) {
68
+ return;
69
+ }
70
+ let dirty = false;
71
+ this._tempWidth = this.size[0];
72
+ dirty = this.stabilizeInputsOutputs();
73
+ const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this);
74
+ dirty = this.handleLinkedNodesStabilization(linkedNodes) || dirty;
75
+ if (dirty) {
76
+ app.graph.setDirtyCanvas(true, true);
77
+ }
78
+ this.scheduleStabilizeWidgets(500);
79
+ }
80
+ handleLinkedNodesStabilization(linkedNodes) {
81
+ linkedNodes;
82
+ throw new Error("handleLinkedNodesStabilization should be overridden.");
83
+ }
84
+ onConnectionsChainChange() {
85
+ this.scheduleStabilizeWidgets();
86
+ }
87
+ onConnectionsChange(type, index, connected, linkInfo, ioSlot) {
88
+ super.onConnectionsChange &&
89
+ super.onConnectionsChange(type, index, connected, linkInfo, ioSlot);
90
+ if (!linkInfo)
91
+ return;
92
+ const connectedNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
93
+ for (const node of connectedNodes) {
94
+ if (node.onConnectionsChainChange) {
95
+ node.onConnectionsChainChange();
96
+ }
97
+ }
98
+ this.scheduleStabilizeWidgets();
99
+ }
100
+ removeInput(slot) {
101
+ this._tempWidth = this.size[0];
102
+ return super.removeInput(slot);
103
+ }
104
+ addInput(name, type, extra_info) {
105
+ this._tempWidth = this.size[0];
106
+ return super.addInput(name, type, extra_info);
107
+ }
108
+ addWidget(type, name, value, callback, options) {
109
+ this._tempWidth = this.size[0];
110
+ return super.addWidget(type, name, value, callback, options);
111
+ }
112
+ removeWidget(widgetOrSlot) {
113
+ this._tempWidth = this.size[0];
114
+ super.removeWidget(widgetOrSlot);
115
+ }
116
+ computeSize(out) {
117
+ var _a, _b;
118
+ let size = super.computeSize(out);
119
+ if (this._tempWidth) {
120
+ size[0] = this._tempWidth;
121
+ this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
122
+ this.debouncerTempWidth = setTimeout(() => {
123
+ this._tempWidth = null;
124
+ }, 32);
125
+ }
126
+ if (this.properties["collapse_connections"]) {
127
+ const rows = Math.max(((_a = this.inputs) === null || _a === void 0 ? void 0 : _a.length) || 0, ((_b = this.outputs) === null || _b === void 0 ? void 0 : _b.length) || 0, 1) - 1;
128
+ size[1] = size[1] - rows * LiteGraph.NODE_SLOT_HEIGHT;
129
+ }
130
+ setTimeout(() => {
131
+ app.graph.setDirtyCanvas(true, true);
132
+ }, 16);
133
+ return size;
134
+ }
135
+ onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
136
+ let canConnect = true;
137
+ if (super.onConnectOutput) {
138
+ canConnect = super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex);
139
+ }
140
+ if (canConnect) {
141
+ const nodes = getConnectedInputNodes(this);
142
+ if (nodes.includes(inputNode)) {
143
+ alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
144
+ `a situation that could create a time paradox, the results of which could cause a ` +
145
+ `chain reaction that would unravel the very fabric of the space time continuum, ` +
146
+ `and destroy the entire universe!`);
147
+ canConnect = false;
148
+ }
149
+ }
150
+ return canConnect;
151
+ }
152
+ onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
153
+ let canConnect = true;
154
+ if (super.onConnectInput) {
155
+ canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
156
+ }
157
+ if (canConnect) {
158
+ const nodes = getConnectedOutputNodes(this);
159
+ if (nodes.includes(outputNode)) {
160
+ alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
161
+ `a situation that could create a time paradox, the results of which could cause a ` +
162
+ `chain reaction that would unravel the very fabric of the space time continuum, ` +
163
+ `and destroy the entire universe!`);
164
+ canConnect = false;
165
+ }
166
+ }
167
+ return canConnect;
168
+ }
169
+ connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
170
+ const lastInput = this.inputs[this.inputs.length - 1];
171
+ if (!(lastInput === null || lastInput === void 0 ? void 0 : lastInput.link) && (lastInput === null || lastInput === void 0 ? void 0 : lastInput.type) === "*") {
172
+ var sourceSlot = sourceNode.findOutputSlotByType(sourceSlotType, false, true);
173
+ return sourceNode.connect(sourceSlot, this, slot);
174
+ }
175
+ return super.connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn);
176
+ }
177
+ static setUp() {
178
+ super.setUp();
179
+ addConnectionLayoutSupport(this, app, [
180
+ ["Left", "Right"],
181
+ ["Right", "Left"],
182
+ ]);
183
+ addMenuItem(this, app, {
184
+ name: (node) => { var _a; return `${((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]) ? "Show" : "Collapse"} Connections`; },
185
+ property: "collapse_connections",
186
+ prepareValue: (_value, node) => { var _a; return !((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]); },
187
+ callback: (_node) => {
188
+ app.graph.setDirtyCanvas(true, true);
189
+ },
190
+ });
191
+ }
192
+ }
193
+ const oldLGraphNodeConnectByType = LGraphNode.prototype.connectByType;
194
+ LGraphNode.prototype.connectByType = function connectByType(slot, sourceNode, sourceSlotType, optsIn) {
195
+ if (sourceNode.inputs) {
196
+ for (const [index, input] of sourceNode.inputs.entries()) {
197
+ if (!input.link && input.type === "*") {
198
+ this.connect(slot, sourceNode, index);
199
+ return null;
200
+ }
201
+ }
202
+ }
203
+ return ((oldLGraphNodeConnectByType &&
204
+ oldLGraphNodeConnectByType.call(this, slot, sourceNode, sourceSlotType, optsIn)) ||
205
+ null);
206
+ };
rgthree-comfy/web/comfyui/base_node.js ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComfyWidgets } from "../../scripts/widgets.js";
2
+ import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
3
+ import { app } from "../../scripts/app.js";
4
+ import { LogLevel, rgthree } from "./rgthree.js";
5
+ import { addHelpMenuItem } from "./utils.js";
6
+ import { RgthreeHelpDialog } from "../../rgthree/common/dialog.js";
7
+ import { importIndividualNodesInnerOnDragDrop, importIndividualNodesInnerOnDragOver, } from "./feature_import_individual_nodes.js";
8
+ import { defineProperty } from "../../rgthree/common/shared_utils.js";
9
+ export class RgthreeBaseNode extends LGraphNode {
10
+ constructor(title = RgthreeBaseNode.title, skipOnConstructedCall = true) {
11
+ super(title);
12
+ this.comfyClass = "__NEED_COMFY_CLASS__";
13
+ this.nickname = "rgthree";
14
+ this.isVirtualNode = false;
15
+ this.isDropEnabled = false;
16
+ this.removed = false;
17
+ this.configuring = false;
18
+ this._tempWidth = 0;
19
+ this.__constructed__ = false;
20
+ this.helpDialog = null;
21
+ if (title == "__NEED_CLASS_TITLE__") {
22
+ throw new Error("RgthreeBaseNode needs overrides.");
23
+ }
24
+ this.widgets = this.widgets || [];
25
+ this.properties = this.properties || {};
26
+ setTimeout(() => {
27
+ if (this.comfyClass == "__NEED_COMFY_CLASS__") {
28
+ throw new Error("RgthreeBaseNode needs a comfy class override.");
29
+ }
30
+ this.checkAndRunOnConstructed();
31
+ });
32
+ defineProperty(this, 'mode', {
33
+ get: () => {
34
+ return this.rgthree_mode;
35
+ },
36
+ set: (mode) => {
37
+ if (this.rgthree_mode != mode) {
38
+ const oldMode = this.rgthree_mode;
39
+ this.rgthree_mode = mode;
40
+ this.onModeChange(oldMode, mode);
41
+ }
42
+ },
43
+ });
44
+ }
45
+ checkAndRunOnConstructed() {
46
+ var _a;
47
+ if (!this.__constructed__) {
48
+ this.onConstructed();
49
+ const [n, v] = rgthree.logger.logParts(LogLevel.DEV, `[RgthreeBaseNode] Child class did not call onConstructed for "${this.type}.`);
50
+ (_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
51
+ }
52
+ return this.__constructed__;
53
+ }
54
+ onDragOver(e) {
55
+ if (!this.isDropEnabled)
56
+ return false;
57
+ return importIndividualNodesInnerOnDragOver(this, e);
58
+ }
59
+ async onDragDrop(e) {
60
+ if (!this.isDropEnabled)
61
+ return false;
62
+ return importIndividualNodesInnerOnDragDrop(this, e);
63
+ }
64
+ onConstructed() {
65
+ var _a;
66
+ if (this.__constructed__)
67
+ return false;
68
+ this.type = (_a = this.type) !== null && _a !== void 0 ? _a : undefined;
69
+ this.__constructed__ = true;
70
+ rgthree.invokeExtensionsAsync("nodeCreated", this);
71
+ return this.__constructed__;
72
+ }
73
+ configure(info) {
74
+ this.configuring = true;
75
+ super.configure(info);
76
+ for (const w of this.widgets || []) {
77
+ w.last_y = w.last_y || 0;
78
+ }
79
+ this.configuring = false;
80
+ }
81
+ clone() {
82
+ const cloned = super.clone();
83
+ if (cloned.properties && !!window.structuredClone) {
84
+ cloned.properties = structuredClone(cloned.properties);
85
+ }
86
+ return cloned;
87
+ }
88
+ onModeChange(from, to) {
89
+ }
90
+ async handleAction(action) {
91
+ action;
92
+ }
93
+ removeWidget(widgetOrSlot) {
94
+ if (typeof widgetOrSlot === "number") {
95
+ this.widgets.splice(widgetOrSlot, 1);
96
+ }
97
+ else if (widgetOrSlot) {
98
+ const index = this.widgets.indexOf(widgetOrSlot);
99
+ if (index > -1) {
100
+ this.widgets.splice(index, 1);
101
+ }
102
+ }
103
+ }
104
+ defaultGetSlotMenuOptions(slot) {
105
+ var _a, _b;
106
+ const menu_info = [];
107
+ if ((_b = (_a = slot === null || slot === void 0 ? void 0 : slot.output) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) {
108
+ menu_info.push({ content: "Disconnect Links", slot: slot });
109
+ }
110
+ let inputOrOutput = slot.input || slot.output;
111
+ if (inputOrOutput) {
112
+ if (inputOrOutput.removable) {
113
+ menu_info.push(inputOrOutput.locked ? { content: "Cannot remove" } : { content: "Remove Slot", slot });
114
+ }
115
+ if (!inputOrOutput.nameLocked) {
116
+ menu_info.push({ content: "Rename Slot", slot });
117
+ }
118
+ }
119
+ return menu_info;
120
+ }
121
+ onRemoved() {
122
+ var _a;
123
+ (_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this);
124
+ this.removed = true;
125
+ }
126
+ static setUp(...args) {
127
+ }
128
+ getHelp() {
129
+ return "";
130
+ }
131
+ showHelp() {
132
+ const help = this.getHelp() || this.constructor.help;
133
+ if (help) {
134
+ this.helpDialog = new RgthreeHelpDialog(this, help).show();
135
+ this.helpDialog.addEventListener("close", (e) => {
136
+ this.helpDialog = null;
137
+ });
138
+ }
139
+ }
140
+ onKeyDown(event) {
141
+ KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
142
+ if (event.key == "?" && !this.helpDialog) {
143
+ this.showHelp();
144
+ }
145
+ }
146
+ onKeyUp(event) {
147
+ KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
148
+ }
149
+ getExtraMenuOptions(canvas, options) {
150
+ var _a, _b, _c, _d, _e, _f;
151
+ if (super.getExtraMenuOptions) {
152
+ (_a = super.getExtraMenuOptions) === null || _a === void 0 ? void 0 : _a.apply(this, [canvas, options]);
153
+ }
154
+ else if ((_c = (_b = this.constructor.nodeType) === null || _b === void 0 ? void 0 : _b.prototype) === null || _c === void 0 ? void 0 : _c.getExtraMenuOptions) {
155
+ (_f = (_e = (_d = this.constructor.nodeType) === null || _d === void 0 ? void 0 : _d.prototype) === null || _e === void 0 ? void 0 : _e.getExtraMenuOptions) === null || _f === void 0 ? void 0 : _f.apply(this, [
156
+ canvas,
157
+ options,
158
+ ]);
159
+ }
160
+ const help = this.getHelp() || this.constructor.help;
161
+ if (help) {
162
+ addHelpMenuItem(this, help, options);
163
+ }
164
+ }
165
+ }
166
+ RgthreeBaseNode.exposedActions = [];
167
+ RgthreeBaseNode.title = "__NEED_CLASS_TITLE__";
168
+ RgthreeBaseNode.category = "rgthree";
169
+ RgthreeBaseNode._category = "rgthree";
170
+ export class RgthreeBaseVirtualNode extends RgthreeBaseNode {
171
+ constructor(title = RgthreeBaseNode.title) {
172
+ super(title, false);
173
+ this.isVirtualNode = true;
174
+ }
175
+ static setUp() {
176
+ if (!this.type) {
177
+ throw new Error(`Missing type for RgthreeBaseVirtualNode: ${this.title}`);
178
+ }
179
+ LiteGraph.registerNodeType(this.type, this);
180
+ if (this._category) {
181
+ this.category = this._category;
182
+ }
183
+ }
184
+ }
185
+ export class RgthreeBaseServerNode extends RgthreeBaseNode {
186
+ constructor(title) {
187
+ super(title, true);
188
+ this.isDropEnabled = true;
189
+ this.serialize_widgets = true;
190
+ this.setupFromServerNodeData();
191
+ this.onConstructed();
192
+ }
193
+ getWidgets() {
194
+ return ComfyWidgets;
195
+ }
196
+ async setupFromServerNodeData() {
197
+ var _a, _b, _c;
198
+ const nodeData = this.constructor.nodeData;
199
+ if (!nodeData) {
200
+ throw Error("No node data");
201
+ }
202
+ this.comfyClass = nodeData.name;
203
+ let inputs = nodeData["input"]["required"];
204
+ if (nodeData["input"]["optional"] != undefined) {
205
+ inputs = Object.assign({}, inputs, nodeData["input"]["optional"]);
206
+ }
207
+ const WIDGETS = this.getWidgets();
208
+ const config = {
209
+ minWidth: 1,
210
+ minHeight: 1,
211
+ widget: null,
212
+ };
213
+ for (const inputName in inputs) {
214
+ const inputData = inputs[inputName];
215
+ const type = inputData[0];
216
+ if ((_a = inputData[1]) === null || _a === void 0 ? void 0 : _a.forceInput) {
217
+ this.addInput(inputName, type);
218
+ }
219
+ else {
220
+ let widgetCreated = true;
221
+ if (Array.isArray(type)) {
222
+ Object.assign(config, WIDGETS.COMBO(this, inputName, inputData, app) || {});
223
+ }
224
+ else if (`${type}:${inputName}` in WIDGETS) {
225
+ Object.assign(config, WIDGETS[`${type}:${inputName}`](this, inputName, inputData, app) || {});
226
+ }
227
+ else if (type in WIDGETS) {
228
+ Object.assign(config, WIDGETS[type](this, inputName, inputData, app) || {});
229
+ }
230
+ else {
231
+ this.addInput(inputName, type);
232
+ widgetCreated = false;
233
+ }
234
+ if (widgetCreated && ((_b = inputData[1]) === null || _b === void 0 ? void 0 : _b.forceInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
235
+ if (!config.widget.options)
236
+ config.widget.options = {};
237
+ config.widget.options.forceInput = inputData[1].forceInput;
238
+ }
239
+ if (widgetCreated && ((_c = inputData[1]) === null || _c === void 0 ? void 0 : _c.defaultInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
240
+ if (!config.widget.options)
241
+ config.widget.options = {};
242
+ config.widget.options.defaultInput = inputData[1].defaultInput;
243
+ }
244
+ }
245
+ }
246
+ for (const o in nodeData["output"]) {
247
+ let output = nodeData["output"][o];
248
+ if (output instanceof Array)
249
+ output = "COMBO";
250
+ const outputName = nodeData["output_name"][o] || output;
251
+ const outputShape = nodeData["output_is_list"][o]
252
+ ? LiteGraph.GRID_SHAPE
253
+ : LiteGraph.CIRCLE_SHAPE;
254
+ this.addOutput(outputName, output, { shape: outputShape });
255
+ }
256
+ const s = this.computeSize();
257
+ s[0] = Math.max(config.minWidth, s[0] * 1.5);
258
+ s[1] = Math.max(config.minHeight, s[1]);
259
+ this.size = s;
260
+ this.serialize_widgets = true;
261
+ }
262
+ static registerForOverride(comfyClass, nodeData, rgthreeClass) {
263
+ if (OVERRIDDEN_SERVER_NODES.has(comfyClass)) {
264
+ throw Error(`Already have a class to override ${comfyClass.type || comfyClass.name || comfyClass.title}`);
265
+ }
266
+ OVERRIDDEN_SERVER_NODES.set(comfyClass, rgthreeClass);
267
+ if (!rgthreeClass.__registeredForOverride__) {
268
+ rgthreeClass.__registeredForOverride__ = true;
269
+ rgthreeClass.nodeType = comfyClass;
270
+ rgthreeClass.nodeData = nodeData;
271
+ rgthreeClass.onRegisteredForOverride(comfyClass, rgthreeClass);
272
+ }
273
+ }
274
+ static onRegisteredForOverride(comfyClass, rgthreeClass) {
275
+ }
276
+ }
277
+ RgthreeBaseServerNode.nodeData = null;
278
+ RgthreeBaseServerNode.nodeType = null;
279
+ RgthreeBaseServerNode.__registeredForOverride__ = false;
280
+ const OVERRIDDEN_SERVER_NODES = new Map();
281
+ const oldregisterNodeType = LiteGraph.registerNodeType;
282
+ LiteGraph.registerNodeType = async function (nodeId, baseClass) {
283
+ var _a;
284
+ const clazz = OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass;
285
+ if (clazz !== baseClass) {
286
+ const classLabel = clazz.type || clazz.name || clazz.title;
287
+ const [n, v] = rgthree.logger.logParts(LogLevel.DEBUG, `${nodeId}: replacing default ComfyNode implementation with custom ${classLabel} class.`);
288
+ (_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
289
+ }
290
+ return oldregisterNodeType.call(LiteGraph, nodeId, clazz);
291
+ };
rgthree-comfy/web/comfyui/base_node_collector.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { rgthree } from "./rgthree.js";
2
+ import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
3
+ import { PassThroughFollowing, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, shouldPassThrough, } from "./utils.js";
4
+ export class BaseCollectorNode extends BaseAnyInputConnectedNode {
5
+ constructor(title) {
6
+ super(title);
7
+ this.inputsPassThroughFollowing = PassThroughFollowing.REROUTE_ONLY;
8
+ this.logger = rgthree.newLogSession("[BaseCollectorNode]");
9
+ }
10
+ clone() {
11
+ const cloned = super.clone();
12
+ return cloned;
13
+ }
14
+ handleLinkedNodesStabilization(linkedNodes) {
15
+ return false;
16
+ }
17
+ onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
18
+ var _a, _b, _c, _d;
19
+ let canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
20
+ if (canConnect) {
21
+ const allConnectedNodes = getConnectedInputNodes(this);
22
+ const nodesAlreadyInSlot = getConnectedInputNodes(this, undefined, inputIndex);
23
+ if (allConnectedNodes.includes(outputNode)) {
24
+ const [n, v] = this.logger.debugParts(`${outputNode.title} is already connected to ${this.title}.`);
25
+ (_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
26
+ if (nodesAlreadyInSlot.includes(outputNode)) {
27
+ const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
28
+ (_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
29
+ }
30
+ else {
31
+ canConnect = false;
32
+ }
33
+ }
34
+ if (canConnect && shouldPassThrough(outputNode, PassThroughFollowing.REROUTE_ONLY)) {
35
+ const connectedNode = getConnectedInputNodesAndFilterPassThroughs(outputNode, undefined, undefined, PassThroughFollowing.REROUTE_ONLY)[0];
36
+ if (connectedNode && allConnectedNodes.includes(connectedNode)) {
37
+ const [n, v] = this.logger.debugParts(`${connectedNode.title} is already connected to ${this.title}.`);
38
+ (_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
39
+ if (nodesAlreadyInSlot.includes(connectedNode)) {
40
+ const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
41
+ (_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
42
+ }
43
+ else {
44
+ canConnect = false;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return canConnect;
50
+ }
51
+ }
rgthree-comfy/web/comfyui/base_node_mode_changer.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
2
+ import { PassThroughFollowing } from "./utils.js";
3
+ import { wait } from "../../rgthree/common/shared_utils.js";
4
+ export class BaseNodeModeChanger extends BaseAnyInputConnectedNode {
5
+ constructor(title) {
6
+ super(title);
7
+ this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
8
+ this.isVirtualNode = true;
9
+ this.modeOn = -1;
10
+ this.modeOff = -1;
11
+ this.properties["toggleRestriction"] = "default";
12
+ }
13
+ onConstructed() {
14
+ wait(10).then(() => {
15
+ if (this.modeOn < 0 || this.modeOff < 0) {
16
+ throw new Error("modeOn and modeOff must be overridden.");
17
+ }
18
+ });
19
+ this.addOutput("OPT_CONNECTION", "*");
20
+ return super.onConstructed();
21
+ }
22
+ configure(info) {
23
+ var _a;
24
+ if ((_a = info.outputs) === null || _a === void 0 ? void 0 : _a.length) {
25
+ info.outputs.length = 1;
26
+ }
27
+ super.configure(info);
28
+ }
29
+ handleLinkedNodesStabilization(linkedNodes) {
30
+ let changed = false;
31
+ for (const [index, node] of linkedNodes.entries()) {
32
+ let widget = this.widgets && this.widgets[index];
33
+ if (!widget) {
34
+ this._tempWidth = this.size[0];
35
+ widget = this.addWidget("toggle", "", false, "", { on: "yes", off: "no" });
36
+ changed = true;
37
+ }
38
+ if (node) {
39
+ changed = this.setWidget(widget, node) || changed;
40
+ }
41
+ }
42
+ if (this.widgets && this.widgets.length > linkedNodes.length) {
43
+ this.widgets.length = linkedNodes.length;
44
+ changed = true;
45
+ }
46
+ return changed;
47
+ }
48
+ setWidget(widget, linkedNode, forceValue) {
49
+ let changed = false;
50
+ const value = forceValue == null ? linkedNode.mode === this.modeOn : forceValue;
51
+ let name = `Enable ${linkedNode.title}`;
52
+ if (widget.name !== name) {
53
+ widget.name = `Enable ${linkedNode.title}`;
54
+ widget.options = { on: "yes", off: "no" };
55
+ widget.value = value;
56
+ widget.doModeChange = (forceValue, skipOtherNodeCheck) => {
57
+ var _a, _b, _c;
58
+ let newValue = forceValue == null ? linkedNode.mode === this.modeOff : forceValue;
59
+ if (skipOtherNodeCheck !== true) {
60
+ if (newValue && ((_b = (_a = this.properties) === null || _a === void 0 ? void 0 : _a["toggleRestriction"]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
61
+ for (const widget of this.widgets) {
62
+ widget.doModeChange(false, true);
63
+ }
64
+ }
65
+ else if (!newValue && ((_c = this.properties) === null || _c === void 0 ? void 0 : _c["toggleRestriction"]) === "always one") {
66
+ newValue = this.widgets.every((w) => !w.value || w === widget);
67
+ }
68
+ }
69
+ linkedNode.mode = (newValue ? this.modeOn : this.modeOff);
70
+ widget.value = newValue;
71
+ };
72
+ widget.callback = () => {
73
+ widget.doModeChange();
74
+ };
75
+ changed = true;
76
+ }
77
+ if (forceValue != null) {
78
+ const newMode = (forceValue ? this.modeOn : this.modeOff);
79
+ if (linkedNode.mode !== newMode) {
80
+ linkedNode.mode = newMode;
81
+ changed = true;
82
+ }
83
+ }
84
+ return changed;
85
+ }
86
+ forceWidgetOff(widget, skipOtherNodeCheck) {
87
+ widget.doModeChange(false, skipOtherNodeCheck);
88
+ }
89
+ forceWidgetOn(widget, skipOtherNodeCheck) {
90
+ widget.doModeChange(true, skipOtherNodeCheck);
91
+ }
92
+ forceWidgetToggle(widget, skipOtherNodeCheck) {
93
+ widget.doModeChange(!widget.value, skipOtherNodeCheck);
94
+ }
95
+ }
96
+ BaseNodeModeChanger.collapsible = false;
97
+ BaseNodeModeChanger["@toggleRestriction"] = {
98
+ type: "combo",
99
+ values: ["default", "max one", "always one"],
100
+ };
rgthree-comfy/web/comfyui/base_power_prompt.js ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api } from "../../scripts/api.js";
2
+ import { wait } from "../../rgthree/common/shared_utils.js";
3
+ import { rgthree } from "./rgthree.js";
4
+ export class PowerPrompt {
5
+ constructor(node, nodeData) {
6
+ this.combos = {};
7
+ this.combosValues = {};
8
+ this.configuring = false;
9
+ this.node = node;
10
+ this.node.properties = this.node.properties || {};
11
+ this.node.properties["combos_filter"] = "";
12
+ this.nodeData = nodeData;
13
+ this.isSimple = this.nodeData.name.includes("Simple");
14
+ this.promptEl = node.widgets[0].inputEl;
15
+ this.addAndHandleKeyboardLoraEditWeight();
16
+ this.patchNodeRefresh();
17
+ const oldConfigure = this.node.configure;
18
+ this.node.configure = (info) => {
19
+ this.configuring = true;
20
+ oldConfigure === null || oldConfigure === void 0 ? void 0 : oldConfigure.apply(this.node, [info]);
21
+ this.configuring = false;
22
+ };
23
+ const oldOnConnectionsChange = this.node.onConnectionsChange;
24
+ this.node.onConnectionsChange = (type, slotIndex, isConnected, link_info, _ioSlot) => {
25
+ oldOnConnectionsChange === null || oldOnConnectionsChange === void 0 ? void 0 : oldOnConnectionsChange.apply(this.node, [type, slotIndex, isConnected, link_info, _ioSlot]);
26
+ this.onNodeConnectionsChange(type, slotIndex, isConnected, link_info, _ioSlot);
27
+ };
28
+ const oldOnConnectInput = this.node.onConnectInput;
29
+ this.node.onConnectInput = (inputIndex, outputType, outputSlot, outputNode, outputIndex) => {
30
+ let canConnect = true;
31
+ if (oldOnConnectInput) {
32
+ canConnect = oldOnConnectInput.apply(this.node, [
33
+ inputIndex,
34
+ outputType,
35
+ outputSlot,
36
+ outputNode,
37
+ outputIndex,
38
+ ]);
39
+ }
40
+ return (this.configuring ||
41
+ rgthree.loadingApiJson ||
42
+ (canConnect && !this.node.inputs[inputIndex].disabled));
43
+ };
44
+ const oldOnConnectOutput = this.node.onConnectOutput;
45
+ this.node.onConnectOutput = (outputIndex, inputType, inputSlot, inputNode, inputIndex) => {
46
+ let canConnect = true;
47
+ if (oldOnConnectOutput) {
48
+ canConnect = oldOnConnectOutput === null || oldOnConnectOutput === void 0 ? void 0 : oldOnConnectOutput.apply(this.node, [
49
+ outputIndex,
50
+ inputType,
51
+ inputSlot,
52
+ inputNode,
53
+ inputIndex,
54
+ ]);
55
+ }
56
+ return (this.configuring ||
57
+ rgthree.loadingApiJson ||
58
+ (canConnect && !this.node.outputs[outputIndex].disabled));
59
+ };
60
+ const onPropertyChanged = this.node.onPropertyChanged;
61
+ this.node.onPropertyChanged = (property, value, prevValue) => {
62
+ onPropertyChanged && onPropertyChanged.call(this, property, value, prevValue);
63
+ if (property === "combos_filter") {
64
+ this.refreshCombos(this.nodeData);
65
+ }
66
+ };
67
+ for (let i = this.node.widgets.length - 1; i >= 0; i--) {
68
+ if (this.shouldRemoveServerWidget(this.node.widgets[i])) {
69
+ this.node.widgets.splice(i, 1);
70
+ }
71
+ }
72
+ this.refreshCombos(nodeData);
73
+ setTimeout(() => {
74
+ this.stabilizeInputsOutputs();
75
+ }, 32);
76
+ }
77
+ onNodeConnectionsChange(_type, _slotIndex, _isConnected, _linkInfo, _ioSlot) {
78
+ this.stabilizeInputsOutputs();
79
+ }
80
+ stabilizeInputsOutputs() {
81
+ if (this.configuring || rgthree.loadingApiJson) {
82
+ return;
83
+ }
84
+ const clipLinked = this.node.inputs.some((i) => i.name.includes("clip") && !!i.link);
85
+ const modelLinked = this.node.inputs.some((i) => i.name.includes("model") && !!i.link);
86
+ for (const output of this.node.outputs) {
87
+ const type = output.type.toLowerCase();
88
+ if (type.includes("model")) {
89
+ output.disabled = !modelLinked;
90
+ }
91
+ else if (type.includes("conditioning")) {
92
+ output.disabled = !clipLinked;
93
+ }
94
+ else if (type.includes("clip")) {
95
+ output.disabled = !clipLinked;
96
+ }
97
+ else if (type.includes("string")) {
98
+ output.color_off = "#7F7";
99
+ output.color_on = "#7F7";
100
+ }
101
+ if (output.disabled) {
102
+ }
103
+ }
104
+ }
105
+ onFreshNodeDefs(event) {
106
+ this.refreshCombos(event.detail[this.nodeData.name]);
107
+ }
108
+ shouldRemoveServerWidget(widget) {
109
+ var _a, _b, _c, _d;
110
+ return (((_a = widget.name) === null || _a === void 0 ? void 0 : _a.startsWith("insert_")) ||
111
+ ((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("target_")) ||
112
+ ((_c = widget.name) === null || _c === void 0 ? void 0 : _c.startsWith("crop_")) ||
113
+ ((_d = widget.name) === null || _d === void 0 ? void 0 : _d.startsWith("values_")));
114
+ }
115
+ refreshCombos(nodeData) {
116
+ var _a, _b, _c;
117
+ this.nodeData = nodeData;
118
+ let filter = null;
119
+ if ((_a = this.node.properties["combos_filter"]) === null || _a === void 0 ? void 0 : _a.trim()) {
120
+ try {
121
+ filter = new RegExp(this.node.properties["combos_filter"].trim(), "i");
122
+ }
123
+ catch (e) {
124
+ console.error(`Could not parse "${filter}" for Regular Expression`, e);
125
+ filter = null;
126
+ }
127
+ }
128
+ let data = Object.assign({}, ((_b = this.nodeData.input) === null || _b === void 0 ? void 0 : _b.optional) || {}, ((_c = this.nodeData.input) === null || _c === void 0 ? void 0 : _c.hidden) || {});
129
+ for (const [key, value] of Object.entries(data)) {
130
+ if (Array.isArray(value[0])) {
131
+ let values = value[0];
132
+ if (key.startsWith("insert")) {
133
+ values = filter
134
+ ? values.filter((v, i) => i < 1 || (i == 1 && v.match(/^disable\s[a-z]/i)) || (filter === null || filter === void 0 ? void 0 : filter.test(v)))
135
+ : values;
136
+ const shouldShow = values.length > 2 || (values.length > 1 && !values[1].match(/^disable\s[a-z]/i));
137
+ if (shouldShow) {
138
+ if (!this.combos[key]) {
139
+ this.combos[key] = this.node.addWidget("combo", key, values, (selected) => {
140
+ if (selected !== values[0] && !selected.match(/^disable\s[a-z]/i)) {
141
+ wait().then(() => {
142
+ if (key.includes("embedding")) {
143
+ this.insertSelectionText(`embedding:${selected}`);
144
+ }
145
+ else if (key.includes("saved")) {
146
+ this.insertSelectionText(this.combosValues[`values_${key}`][values.indexOf(selected)]);
147
+ }
148
+ else if (key.includes("lora")) {
149
+ this.insertSelectionText(`<lora:${selected}:1.0>`);
150
+ }
151
+ this.combos[key].value = values[0];
152
+ });
153
+ }
154
+ }, {
155
+ values,
156
+ serialize: true,
157
+ });
158
+ this.combos[key].oldComputeSize = this.combos[key].computeSize;
159
+ let node = this.node;
160
+ this.combos[key].computeSize = function (width) {
161
+ var _a, _b;
162
+ const size = ((_b = (_a = this).oldComputeSize) === null || _b === void 0 ? void 0 : _b.call(_a, width)) || [
163
+ width,
164
+ LiteGraph.NODE_WIDGET_HEIGHT,
165
+ ];
166
+ if (this === node.widgets[node.widgets.length - 1]) {
167
+ size[1] += 10;
168
+ }
169
+ return size;
170
+ };
171
+ }
172
+ this.combos[key].options.values = values;
173
+ this.combos[key].value = values[0];
174
+ }
175
+ else if (!shouldShow && this.combos[key]) {
176
+ this.node.widgets.splice(this.node.widgets.indexOf(this.combos[key]), 1);
177
+ delete this.combos[key];
178
+ }
179
+ }
180
+ else if (key.startsWith("values")) {
181
+ this.combosValues[key] = values;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ insertSelectionText(text) {
187
+ if (!this.promptEl) {
188
+ console.error("Asked to insert text, but no textbox found.");
189
+ return;
190
+ }
191
+ let prompt = this.promptEl.value;
192
+ let first = prompt.substring(0, this.promptEl.selectionEnd).replace(/ +$/, "");
193
+ first = first + (["\n"].includes(first[first.length - 1]) ? "" : first.length ? " " : "");
194
+ let second = prompt.substring(this.promptEl.selectionEnd).replace(/^ +/, "");
195
+ second = (["\n"].includes(second[0]) ? "" : second.length ? " " : "") + second;
196
+ this.promptEl.value = first + text + second;
197
+ this.promptEl.focus();
198
+ this.promptEl.selectionStart = first.length;
199
+ this.promptEl.selectionEnd = first.length + text.length;
200
+ }
201
+ addAndHandleKeyboardLoraEditWeight() {
202
+ this.promptEl.addEventListener("keydown", (event) => {
203
+ var _a, _b;
204
+ if (!(event.key === "ArrowUp" || event.key === "ArrowDown"))
205
+ return;
206
+ if (!event.ctrlKey && !event.metaKey)
207
+ return;
208
+ const delta = event.shiftKey ? 0.01 : 0.1;
209
+ let start = this.promptEl.selectionStart;
210
+ let end = this.promptEl.selectionEnd;
211
+ let fullText = this.promptEl.value;
212
+ let selectedText = fullText.substring(start, end);
213
+ if (!selectedText) {
214
+ const stopOn = "<>()\r\n\t";
215
+ if (fullText[start] == ">") {
216
+ start -= 2;
217
+ end -= 2;
218
+ }
219
+ if (fullText[end - 1] == "<") {
220
+ start += 2;
221
+ end += 2;
222
+ }
223
+ while (!stopOn.includes(fullText[start]) && start > 0) {
224
+ start--;
225
+ }
226
+ while (!stopOn.includes(fullText[end - 1]) && end < fullText.length) {
227
+ end++;
228
+ }
229
+ selectedText = fullText.substring(start, end);
230
+ }
231
+ if (!selectedText.startsWith("<lora:") || !selectedText.endsWith(">")) {
232
+ return;
233
+ }
234
+ let weight = (_b = Number((_a = selectedText.match(/:(-?\d*(\.\d*)?)>$/)) === null || _a === void 0 ? void 0 : _a[1])) !== null && _b !== void 0 ? _b : 1;
235
+ weight += event.key === "ArrowUp" ? delta : -delta;
236
+ const updatedText = selectedText.replace(/(:-?\d*(\.\d*)?)?>$/, `:${weight.toFixed(2)}>`);
237
+ this.promptEl.setRangeText(updatedText, start, end, "select");
238
+ event.preventDefault();
239
+ event.stopPropagation();
240
+ });
241
+ }
242
+ patchNodeRefresh() {
243
+ this.boundOnFreshNodeDefs = this.onFreshNodeDefs.bind(this);
244
+ api.addEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
245
+ const oldNodeRemoved = this.node.onRemoved;
246
+ this.node.onRemoved = () => {
247
+ oldNodeRemoved === null || oldNodeRemoved === void 0 ? void 0 : oldNodeRemoved.call(this.node);
248
+ api.removeEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
249
+ };
250
+ }
251
+ }
rgthree-comfy/web/comfyui/bookmark.js ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { RgthreeBaseVirtualNode } from "./base_node.js";
3
+ import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
4
+ import { NodeTypesString } from "./constants.js";
5
+ import { getClosestOrSelf, queryOne } from "../../rgthree/common/utils_dom.js";
6
+ export class Bookmark extends RgthreeBaseVirtualNode {
7
+ get _collapsed_width() {
8
+ return this.___collapsed_width;
9
+ }
10
+ set _collapsed_width(width) {
11
+ const canvas = app.canvas;
12
+ const ctx = canvas.canvas.getContext("2d");
13
+ const oldFont = ctx.font;
14
+ ctx.font = canvas.title_text_font;
15
+ this.___collapsed_width = 40 + ctx.measureText(this.title).width;
16
+ ctx.font = oldFont;
17
+ }
18
+ constructor(title = Bookmark.title) {
19
+ super(title);
20
+ this.comfyClass = NodeTypesString.BOOKMARK;
21
+ this.___collapsed_width = 0;
22
+ this.isVirtualNode = true;
23
+ this.serialize_widgets = true;
24
+ const nextShortcutChar = getNextShortcut();
25
+ this.addWidget("text", "shortcut_key", nextShortcutChar, (value, ...args) => {
26
+ value = value.trim()[0] || "1";
27
+ }, {
28
+ y: 8,
29
+ });
30
+ this.addWidget("number", "zoom", 1, (value) => { }, {
31
+ y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
32
+ max: 2,
33
+ min: 0.5,
34
+ precision: 2,
35
+ });
36
+ this.keypressBound = this.onKeypress.bind(this);
37
+ this.title = "🔖";
38
+ this.onConstructed();
39
+ }
40
+ get shortcutKey() {
41
+ var _a, _b, _c;
42
+ return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : "";
43
+ }
44
+ onAdded(graph) {
45
+ KEY_EVENT_SERVICE.addEventListener("keydown", this.keypressBound);
46
+ }
47
+ onRemoved() {
48
+ KEY_EVENT_SERVICE.removeEventListener("keydown", this.keypressBound);
49
+ }
50
+ onKeypress(event) {
51
+ const originalEvent = event.detail.originalEvent;
52
+ const target = originalEvent.target;
53
+ if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
54
+ return;
55
+ }
56
+ if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0].value, true)) {
57
+ this.canvasToBookmark();
58
+ originalEvent.preventDefault();
59
+ originalEvent.stopPropagation();
60
+ }
61
+ }
62
+ onMouseDown(event, pos, graphCanvas) {
63
+ var _a;
64
+ const input = queryOne(".graphdialog > input.value");
65
+ if (input && input.value === ((_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value)) {
66
+ input.addEventListener("keydown", (e) => {
67
+ KEY_EVENT_SERVICE.handleKeyDownOrUp(e);
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + ");
71
+ });
72
+ }
73
+ }
74
+ canvasToBookmark() {
75
+ var _a, _b;
76
+ const canvas = app.canvas;
77
+ if ((_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.offset) {
78
+ canvas.ds.offset[0] = -this.pos[0] + 16;
79
+ canvas.ds.offset[1] = -this.pos[1] + 40;
80
+ }
81
+ if (((_b = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) != null) {
82
+ canvas.ds.scale = Number(this.widgets[1].value || 1);
83
+ }
84
+ canvas.setDirty(true, true);
85
+ }
86
+ }
87
+ Bookmark.type = NodeTypesString.BOOKMARK;
88
+ Bookmark.title = NodeTypesString.BOOKMARK;
89
+ Bookmark.slot_start_y = -20;
90
+ app.registerExtension({
91
+ name: "rgthree.Bookmark",
92
+ registerCustomNodes() {
93
+ Bookmark.setUp();
94
+ },
95
+ });
96
+ function isBookmark(node) {
97
+ return node.type === NodeTypesString.BOOKMARK;
98
+ }
99
+ function getExistingShortcuts() {
100
+ const graph = app.graph;
101
+ const bookmarkNodes = graph._nodes.filter(isBookmark);
102
+ const usedShortcuts = new Set(bookmarkNodes.map((n) => n.shortcutKey));
103
+ return usedShortcuts;
104
+ }
105
+ const SHORTCUT_DEFAULTS = "1234567890abcdefghijklmnopqrstuvwxyz".split("");
106
+ function getNextShortcut() {
107
+ var _a;
108
+ const existingShortcuts = getExistingShortcuts();
109
+ return (_a = SHORTCUT_DEFAULTS.find((char) => !existingShortcuts.has(char))) !== null && _a !== void 0 ? _a : "1";
110
+ }
rgthree-comfy/web/comfyui/bypasser.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
3
+ import { NodeTypesString } from "./constants.js";
4
+ const MODE_BYPASS = 4;
5
+ const MODE_ALWAYS = 0;
6
+ class BypasserNode extends BaseNodeModeChanger {
7
+ constructor(title = BypasserNode.title) {
8
+ super(title);
9
+ this.comfyClass = NodeTypesString.FAST_BYPASSER;
10
+ this.modeOn = MODE_ALWAYS;
11
+ this.modeOff = MODE_BYPASS;
12
+ this.onConstructed();
13
+ }
14
+ async handleAction(action) {
15
+ if (action === "Bypass all") {
16
+ for (const widget of this.widgets) {
17
+ this.forceWidgetOff(widget, true);
18
+ }
19
+ }
20
+ else if (action === "Enable all") {
21
+ for (const widget of this.widgets) {
22
+ this.forceWidgetOn(widget, true);
23
+ }
24
+ }
25
+ else if (action === "Toggle all") {
26
+ for (const widget of this.widgets) {
27
+ this.forceWidgetToggle(widget, true);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ BypasserNode.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
33
+ BypasserNode.type = NodeTypesString.FAST_BYPASSER;
34
+ BypasserNode.title = NodeTypesString.FAST_BYPASSER;
35
+ app.registerExtension({
36
+ name: "rgthree.Bypasser",
37
+ registerCustomNodes() {
38
+ BypasserNode.setUp();
39
+ },
40
+ loadedGraphNode(node) {
41
+ if (node.type == BypasserNode.title) {
42
+ node._tempWidth = node.size[0];
43
+ }
44
+ },
45
+ });
rgthree-comfy/web/comfyui/comfy_ui_bar.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { ComfyButtonGroup } from "../../scripts/ui/components/buttonGroup.js";
3
+ import { ComfyButton } from "../../scripts/ui/components/button.js";
4
+ import { iconGear, iconStarFilled, logoRgthree } from "../../rgthree/common/media/svgs.js";
5
+ import { createElement, empty } from "../../rgthree/common/utils_dom.js";
6
+ import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
7
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
8
+ import { ComfyPopup } from "../../scripts/ui/components/popup.js";
9
+ import { RgthreeConfigDialog } from "./config.js";
10
+ let rgthreeButtonGroup = null;
11
+ function addRgthreeTopBarButtons() {
12
+ var _a, _b, _c;
13
+ if (!CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.enabled")) {
14
+ if ((_a = rgthreeButtonGroup === null || rgthreeButtonGroup === void 0 ? void 0 : rgthreeButtonGroup.element) === null || _a === void 0 ? void 0 : _a.parentElement) {
15
+ rgthreeButtonGroup.element.parentElement.removeChild(rgthreeButtonGroup.element);
16
+ }
17
+ return;
18
+ }
19
+ else if (rgthreeButtonGroup) {
20
+ (_b = app.menu) === null || _b === void 0 ? void 0 : _b.settingsGroup.element.before(rgthreeButtonGroup.element);
21
+ return;
22
+ }
23
+ const buttons = [];
24
+ const rgthreeButton = new ComfyButton({
25
+ icon: "rgthree",
26
+ tooltip: "rgthree-comfy",
27
+ app,
28
+ enabled: true,
29
+ classList: "comfyui-button comfyui-menu-mobile-collapse primary",
30
+ });
31
+ buttons.push(rgthreeButton);
32
+ rgthreeButton.iconElement.style.width = "1.2rem";
33
+ rgthreeButton.iconElement.innerHTML = logoRgthree;
34
+ rgthreeButton.withPopup(new ComfyPopup({ target: rgthreeButton.element, classList: "rgthree-top-menu" }, createElement("menu", {
35
+ children: [
36
+ createElement("li", {
37
+ child: createElement("button.rgthree-button-reset", {
38
+ html: iconGear + "Settings (rgthree-comfy)",
39
+ onclick: () => new RgthreeConfigDialog().show(),
40
+ }),
41
+ }),
42
+ createElement("li", {
43
+ child: createElement("button.rgthree-button-reset", {
44
+ html: iconStarFilled + "Star on Github",
45
+ onclick: () => window.open("https://github.com/rgthree/rgthree-comfy", "_blank"),
46
+ }),
47
+ }),
48
+ ],
49
+ })), "click");
50
+ if (CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.button_bookmarks.enabled")) {
51
+ const bookmarksListEl = createElement("menu");
52
+ bookmarksListEl.appendChild(createElement("li.rgthree-message", {
53
+ child: createElement("span", { text: "No bookmarks in current workflow." }),
54
+ }));
55
+ const bookmarksButton = new ComfyButton({
56
+ icon: "bookmark",
57
+ tooltip: "Workflow Bookmarks (rgthree-comfy)",
58
+ app,
59
+ });
60
+ const bookmarksPopup = new ComfyPopup({ target: bookmarksButton.element, classList: "rgthree-top-menu" }, bookmarksListEl);
61
+ bookmarksPopup.addEventListener("open", () => {
62
+ const bookmarks = BOOKMARKS_SERVICE.getCurrentBookmarks();
63
+ empty(bookmarksListEl);
64
+ if (bookmarks.length) {
65
+ for (const b of bookmarks) {
66
+ bookmarksListEl.appendChild(createElement("li", {
67
+ child: createElement("button.rgthree-button-reset", {
68
+ text: `[${b.shortcutKey}] ${b.title}`,
69
+ onclick: () => {
70
+ b.canvasToBookmark();
71
+ },
72
+ }),
73
+ }));
74
+ }
75
+ }
76
+ else {
77
+ bookmarksListEl.appendChild(createElement("li.rgthree-message", {
78
+ child: createElement("span", { text: "No bookmarks in current workflow." }),
79
+ }));
80
+ }
81
+ bookmarksPopup.update();
82
+ });
83
+ bookmarksButton.withPopup(bookmarksPopup, "hover");
84
+ buttons.push(bookmarksButton);
85
+ }
86
+ rgthreeButtonGroup = new ComfyButtonGroup(...buttons);
87
+ (_c = app.menu) === null || _c === void 0 ? void 0 : _c.settingsGroup.element.before(rgthreeButtonGroup.element);
88
+ }
89
+ app.registerExtension({
90
+ name: "rgthree.TopMenu",
91
+ async setup() {
92
+ addRgthreeTopBarButtons();
93
+ CONFIG_SERVICE.addEventListener("config-change", ((e) => {
94
+ var _a, _b;
95
+ if ((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key) === null || _b === void 0 ? void 0 : _b.includes("features.comfy_top_bar_menu")) {
96
+ addRgthreeTopBarButtons();
97
+ }
98
+ }));
99
+ },
100
+ });
rgthree-comfy/web/comfyui/config.js ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { RgthreeDialog } from "../../rgthree/common/dialog.js";
3
+ import { createElement as $el, query as $$ } from "../../rgthree/common/utils_dom.js";
4
+ import { checkmark, logoRgthree } from "../../rgthree/common/media/svgs.js";
5
+ import { rgthree } from "./rgthree.js";
6
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
7
+ var ConfigType;
8
+ (function (ConfigType) {
9
+ ConfigType[ConfigType["UNKNOWN"] = 0] = "UNKNOWN";
10
+ ConfigType[ConfigType["BOOLEAN"] = 1] = "BOOLEAN";
11
+ ConfigType[ConfigType["STRING"] = 2] = "STRING";
12
+ ConfigType[ConfigType["NUMBER"] = 3] = "NUMBER";
13
+ ConfigType[ConfigType["ARRAY"] = 4] = "ARRAY";
14
+ })(ConfigType || (ConfigType = {}));
15
+ var ConfigInputType;
16
+ (function (ConfigInputType) {
17
+ ConfigInputType[ConfigInputType["UNKNOWN"] = 0] = "UNKNOWN";
18
+ ConfigInputType[ConfigInputType["CHECKLIST"] = 1] = "CHECKLIST";
19
+ })(ConfigInputType || (ConfigInputType = {}));
20
+ const TYPE_TO_STRING = {
21
+ [ConfigType.UNKNOWN]: "unknown",
22
+ [ConfigType.BOOLEAN]: "boolean",
23
+ [ConfigType.STRING]: "string",
24
+ [ConfigType.NUMBER]: "number",
25
+ [ConfigType.ARRAY]: "array",
26
+ };
27
+ const CONFIGURABLE = {
28
+ features: [
29
+ {
30
+ key: "features.progress_bar.enabled",
31
+ type: ConfigType.BOOLEAN,
32
+ label: "Prompt Progress Bar",
33
+ description: `Shows a minimal progress bar for nodes and steps at the top of the app.`,
34
+ subconfig: [
35
+ {
36
+ key: "features.progress_bar.height",
37
+ type: ConfigType.NUMBER,
38
+ label: "Height of the bar",
39
+ },
40
+ {
41
+ key: "features.progress_bar.position",
42
+ type: ConfigType.STRING,
43
+ label: "Position at top or bottom of window",
44
+ options: ["top", "bottom"],
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ key: "features.import_individual_nodes.enabled",
50
+ type: ConfigType.BOOLEAN,
51
+ label: "Import Individual Nodes Widgets",
52
+ description: "Dragging & Dropping a similar image/JSON workflow onto (most) current workflow nodes" +
53
+ "will allow you to import that workflow's node's widgets when it has the same " +
54
+ "id and type. This is useful when you have several images and you'd like to import just " +
55
+ "one part of a previous iteration, like a seed, or prompt.",
56
+ },
57
+ ],
58
+ menus: [
59
+ {
60
+ key: "features.comfy_top_bar_menu.enabled",
61
+ type: ConfigType.BOOLEAN,
62
+ label: "Enable Top Bar Menu",
63
+ description: "Have quick access from ComfyUI's new top bar to rgthree-comfy bookmarks, settings " +
64
+ "(and more to come).",
65
+ },
66
+ {
67
+ key: "features.menu_queue_selected_nodes",
68
+ type: ConfigType.BOOLEAN,
69
+ label: "Show 'Queue Selected Output Nodes'",
70
+ description: "Will show a menu item in the right-click context menus to queue (only) the selected " +
71
+ "output nodes.",
72
+ },
73
+ {
74
+ key: "features.menu_auto_nest.subdirs",
75
+ type: ConfigType.BOOLEAN,
76
+ label: "Auto Nest Subdirectories in Menus",
77
+ description: "When a large, flat list of values contain sub-directories, auto nest them. (Like, for " +
78
+ "a large list of checkpoints).",
79
+ subconfig: [
80
+ {
81
+ key: "features.menu_auto_nest.threshold",
82
+ type: ConfigType.NUMBER,
83
+ label: "Number of items needed to trigger nesting.",
84
+ },
85
+ ],
86
+ },
87
+ {
88
+ key: "features.menu_bookmarks.enabled",
89
+ type: ConfigType.BOOLEAN,
90
+ label: "Show Bookmarks in context menu",
91
+ description: "Will list bookmarks in the rgthree-comfy right-click context menu.",
92
+ },
93
+ ],
94
+ groups: [
95
+ {
96
+ key: "features.group_header_fast_toggle.enabled",
97
+ type: ConfigType.BOOLEAN,
98
+ label: "Show fast toggles in Group Headers",
99
+ description: "Show quick toggles in Groups' Headers to quickly mute, bypass or queue.",
100
+ subconfig: [
101
+ {
102
+ key: "features.group_header_fast_toggle.toggles",
103
+ type: ConfigType.ARRAY,
104
+ label: "Which toggles to show.",
105
+ inputType: ConfigInputType.CHECKLIST,
106
+ options: [
107
+ { value: "queue", label: "queue" },
108
+ { value: "bypass", label: "bypass" },
109
+ { value: "mute", label: "mute" },
110
+ ],
111
+ },
112
+ {
113
+ key: "features.group_header_fast_toggle.show",
114
+ type: ConfigType.STRING,
115
+ label: "When to show them.",
116
+ options: [
117
+ { value: "hover", label: "on hover" },
118
+ { value: "always", label: "always" },
119
+ ],
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ advanced: [
125
+ {
126
+ key: "features.show_alerts_for_corrupt_workflows",
127
+ type: ConfigType.BOOLEAN,
128
+ label: "Detect Corrupt Workflows",
129
+ description: "Will show a message at the top of the screen when loading a workflow that has " +
130
+ "corrupt linking data.",
131
+ },
132
+ {
133
+ key: "log_level",
134
+ type: ConfigType.STRING,
135
+ label: "Log level for browser dev console.",
136
+ description: "Further down the list, the more verbose logs to the console will be. For instance, " +
137
+ "selecting 'IMPORTANT' means only important message will be logged to the browser " +
138
+ "console, while selecting 'WARN' will log all messages at or higher than WARN, including " +
139
+ "'ERROR' and 'IMPORTANT' etc.",
140
+ options: ["IMPORTANT", "ERROR", "WARN", "INFO", "DEBUG", "DEV"],
141
+ isDevOnly: true,
142
+ onSave: function (value) {
143
+ rgthree.setLogLevel(value);
144
+ },
145
+ },
146
+ {
147
+ key: "features.invoke_extensions_async.node_created",
148
+ type: ConfigType.BOOLEAN,
149
+ label: "Allow other extensions to call nodeCreated on rgthree-nodes.",
150
+ isDevOnly: true,
151
+ description: "Do not disable unless you are having trouble (and then file an issue at rgthree-comfy)." +
152
+ "Prior to Apr 2024 it was not possible for other extensions to invoke their nodeCreated " +
153
+ "event on some rgthree-comfy nodes. Now it's possible and this option is only here in " +
154
+ "for easy if something is wrong.",
155
+ },
156
+ ],
157
+ };
158
+ function fieldrow(item) {
159
+ var _a;
160
+ const initialValue = CONFIG_SERVICE.getConfigValue(item.key);
161
+ const container = $el(`div.fieldrow.-type-${TYPE_TO_STRING[item.type]}`, {
162
+ dataset: {
163
+ name: item.key,
164
+ initial: initialValue,
165
+ type: item.type,
166
+ },
167
+ });
168
+ $el(`label[for="${item.key}"]`, {
169
+ children: [
170
+ $el(`span[text="${item.label}"]`),
171
+ item.description ? $el("small", { html: item.description }) : null,
172
+ ],
173
+ parent: container,
174
+ });
175
+ let input;
176
+ if ((_a = item.options) === null || _a === void 0 ? void 0 : _a.length) {
177
+ if (item.inputType === ConfigInputType.CHECKLIST) {
178
+ const initialValueList = initialValue || [];
179
+ input = $el(`fieldset.rgthree-checklist-group[id="${item.key}"]`, {
180
+ parent: container,
181
+ children: item.options.map((o) => {
182
+ const label = o.label || String(o);
183
+ const value = o.value || o;
184
+ const id = `${item.key}_${value}`;
185
+ return $el(`span.rgthree-checklist-item`, {
186
+ children: [
187
+ $el(`input[type="checkbox"][value="${value}"]`, {
188
+ id,
189
+ checked: initialValueList.includes(value),
190
+ }),
191
+ $el(`label`, {
192
+ for: id,
193
+ text: label,
194
+ })
195
+ ]
196
+ });
197
+ }),
198
+ });
199
+ }
200
+ else {
201
+ input = $el(`select[id="${item.key}"]`, {
202
+ parent: container,
203
+ children: item.options.map((o) => {
204
+ const label = o.label || String(o);
205
+ const value = o.value || o;
206
+ const valueSerialized = JSON.stringify({ value: value });
207
+ return $el(`option[value="${valueSerialized}"]`, {
208
+ text: label,
209
+ selected: valueSerialized === JSON.stringify({ value: initialValue }),
210
+ });
211
+ }),
212
+ });
213
+ }
214
+ }
215
+ else if (item.type === ConfigType.BOOLEAN) {
216
+ container.classList.toggle("-checked", !!initialValue);
217
+ input = $el(`input[type="checkbox"][id="${item.key}"]`, {
218
+ parent: container,
219
+ checked: initialValue,
220
+ });
221
+ }
222
+ else {
223
+ input = $el(`input[id="${item.key}"]`, {
224
+ parent: container,
225
+ value: initialValue,
226
+ });
227
+ }
228
+ $el("div.fieldrow-value", { children: [input], parent: container });
229
+ return container;
230
+ }
231
+ export class RgthreeConfigDialog extends RgthreeDialog {
232
+ constructor() {
233
+ const content = $el("div");
234
+ content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["features"], "Features"));
235
+ content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["menus"], "Menus"));
236
+ content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["groups"], "Groups"));
237
+ content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["advanced"], "Advanced"));
238
+ content.addEventListener("input", (e) => {
239
+ const changed = this.getChangedFormData();
240
+ $$(".save-button", this.element)[0].disabled =
241
+ !Object.keys(changed).length;
242
+ });
243
+ content.addEventListener("change", (e) => {
244
+ const changed = this.getChangedFormData();
245
+ $$(".save-button", this.element)[0].disabled =
246
+ !Object.keys(changed).length;
247
+ });
248
+ const dialogOptions = {
249
+ class: "-iconed -settings",
250
+ title: logoRgthree + `<h2>Settings - rgthree-comfy</h2>`,
251
+ content,
252
+ onBeforeClose: () => {
253
+ const changed = this.getChangedFormData();
254
+ if (Object.keys(changed).length) {
255
+ return confirm("Looks like there are unsaved changes. Are you sure you want close?");
256
+ }
257
+ return true;
258
+ },
259
+ buttons: [
260
+ {
261
+ label: "Save",
262
+ disabled: true,
263
+ className: "rgthree-button save-button -blue",
264
+ callback: async (e) => {
265
+ var _a, _b;
266
+ const changed = this.getChangedFormData();
267
+ if (!Object.keys(changed).length) {
268
+ this.close();
269
+ return;
270
+ }
271
+ const success = await CONFIG_SERVICE.setConfigValues(changed);
272
+ if (success) {
273
+ for (const key of Object.keys(changed)) {
274
+ (_b = (_a = Object.values(CONFIGURABLE)
275
+ .flat()
276
+ .find((f) => f.key === key)) === null || _a === void 0 ? void 0 : _a.onSave) === null || _b === void 0 ? void 0 : _b.call(_a, changed[key]);
277
+ }
278
+ this.close();
279
+ rgthree.showMessage({
280
+ id: "config-success",
281
+ message: `${checkmark} Successfully saved rgthree-comfy settings!`,
282
+ timeout: 4000,
283
+ });
284
+ $$(".save-button", this.element)[0].disabled = true;
285
+ }
286
+ else {
287
+ alert("There was an error saving rgthree-comfy configuration.");
288
+ }
289
+ },
290
+ },
291
+ ],
292
+ };
293
+ super(dialogOptions);
294
+ }
295
+ static buildFieldset(datas, label) {
296
+ const fieldset = $el(`fieldset`, { children: [$el(`legend[text="${label}"]`)] });
297
+ for (const data of datas) {
298
+ if (data.isDevOnly && !rgthree.isDevMode()) {
299
+ continue;
300
+ }
301
+ const container = $el("div.formrow");
302
+ container.appendChild(fieldrow(data));
303
+ if (data.subconfig) {
304
+ for (const subfeature of data.subconfig) {
305
+ container.appendChild(fieldrow(subfeature));
306
+ }
307
+ }
308
+ fieldset.appendChild(container);
309
+ }
310
+ return fieldset;
311
+ }
312
+ getChangedFormData() {
313
+ return $$("[data-name]", this.contentElement).reduce((acc, el) => {
314
+ const name = el.dataset["name"];
315
+ const type = el.dataset["type"];
316
+ const initialValue = CONFIG_SERVICE.getConfigValue(name);
317
+ let currentValueEl = $$("fieldset.rgthree-checklist-group, input, textarea, select", el)[0];
318
+ let currentValue = null;
319
+ if (type === String(ConfigType.BOOLEAN)) {
320
+ currentValue = currentValueEl.checked;
321
+ el.classList.toggle("-checked", currentValue);
322
+ }
323
+ else {
324
+ currentValue = currentValueEl === null || currentValueEl === void 0 ? void 0 : currentValueEl.value;
325
+ if (currentValueEl.nodeName === "SELECT") {
326
+ currentValue = JSON.parse(currentValue).value;
327
+ }
328
+ else if (currentValueEl.classList.contains('rgthree-checklist-group')) {
329
+ currentValue = [];
330
+ for (const check of $$('input[type="checkbox"]', currentValueEl)) {
331
+ if (check.checked) {
332
+ currentValue.push(check.value);
333
+ }
334
+ }
335
+ }
336
+ else if (type === String(ConfigType.NUMBER)) {
337
+ currentValue = Number(currentValue) || initialValue;
338
+ }
339
+ }
340
+ if (JSON.stringify(currentValue) !== JSON.stringify(initialValue)) {
341
+ acc[name] = currentValue;
342
+ }
343
+ return acc;
344
+ }, {});
345
+ }
346
+ }
347
+ app.ui.settings.addSetting({
348
+ id: "rgthree.config",
349
+ name: "Open rgthree-comfy config",
350
+ type: () => {
351
+ return $el("tr.rgthree-comfyui-settings-row", {
352
+ children: [
353
+ $el("td", {
354
+ child: `<div>${logoRgthree} [rgthree-comfy] configuration / settings</div>`,
355
+ }),
356
+ $el("td", {
357
+ child: $el('button.rgthree-button.-blue[text="rgthree-comfy settings"]', {
358
+ events: {
359
+ click: (e) => {
360
+ new RgthreeConfigDialog().show();
361
+ },
362
+ },
363
+ }),
364
+ }),
365
+ ],
366
+ });
367
+ },
368
+ });
rgthree-comfy/web/comfyui/constants.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
2
+ export function addRgthree(str) {
3
+ return str + " (rgthree)";
4
+ }
5
+ export function stripRgthree(str) {
6
+ return str.replace(/\s*\(rgthree\)$/, "");
7
+ }
8
+ export const NodeTypesString = {
9
+ ANY_SWITCH: addRgthree("Any Switch"),
10
+ CONTEXT: addRgthree("Context"),
11
+ CONTEXT_BIG: addRgthree("Context Big"),
12
+ CONTEXT_SWITCH: addRgthree("Context Switch"),
13
+ CONTEXT_SWITCH_BIG: addRgthree("Context Switch Big"),
14
+ CONTEXT_MERGE: addRgthree("Context Merge"),
15
+ CONTEXT_MERGE_BIG: addRgthree("Context Merge Big"),
16
+ DYNAMIC_CONTEXT: addRgthree("Dynamic Context"),
17
+ DYNAMIC_CONTEXT_SWITCH: addRgthree("Dynamic Context Switch"),
18
+ DISPLAY_ANY: addRgthree("Display Any"),
19
+ NODE_MODE_RELAY: addRgthree("Mute / Bypass Relay"),
20
+ NODE_MODE_REPEATER: addRgthree("Mute / Bypass Repeater"),
21
+ FAST_MUTER: addRgthree("Fast Muter"),
22
+ FAST_BYPASSER: addRgthree("Fast Bypasser"),
23
+ FAST_GROUPS_MUTER: addRgthree("Fast Groups Muter"),
24
+ FAST_GROUPS_BYPASSER: addRgthree("Fast Groups Bypasser"),
25
+ FAST_ACTIONS_BUTTON: addRgthree("Fast Actions Button"),
26
+ LABEL: addRgthree("Label"),
27
+ POWER_PROMPT: addRgthree("Power Prompt"),
28
+ POWER_PROMPT_SIMPLE: addRgthree("Power Prompt - Simple"),
29
+ SDXL_EMPTY_LATENT_IMAGE: addRgthree("SDXL Empty Latent Image"),
30
+ SDXL_POWER_PROMPT_POSITIVE: addRgthree("SDXL Power Prompt - Positive"),
31
+ SDXL_POWER_PROMPT_NEGATIVE: addRgthree("SDXL Power Prompt - Simple / Negative"),
32
+ POWER_LORA_LOADER: addRgthree("Power Lora Loader"),
33
+ KSAMPLER_CONFIG: addRgthree("KSampler Config"),
34
+ NODE_COLLECTOR: addRgthree("Node Collector"),
35
+ REROUTE: addRgthree("Reroute"),
36
+ RANDOM_UNMUTER: addRgthree("Random Unmuter"),
37
+ SEED: addRgthree("Seed"),
38
+ BOOKMARK: addRgthree("Bookmark"),
39
+ IMAGE_COMPARER: addRgthree("Image Comparer"),
40
+ IMAGE_INSET_CROP: addRgthree("Image Inset Crop"),
41
+ };
42
+ export function getNodeTypeStrings() {
43
+ return Object.values(NodeTypesString)
44
+ .map((i) => stripRgthree(i))
45
+ .filter((i) => {
46
+ if (i.startsWith("Dynamic Context") &&
47
+ !CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
48
+ return false;
49
+ }
50
+ return true;
51
+ })
52
+ .sort();
53
+ }
rgthree-comfy/web/comfyui/context.js ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { IoDirection, addConnectionLayoutSupport, addMenuItem, matchLocalSlotsToServer, replaceNode, } from "./utils.js";
3
+ import { RgthreeBaseServerNode } from "./base_node.js";
4
+ import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
5
+ import { debounce, wait } from "../../rgthree/common/shared_utils.js";
6
+ import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
7
+ import { NodeTypesString } from "./constants.js";
8
+ function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
9
+ const otherNodeType = (otherNode.type || "").toUpperCase();
10
+ const otherNodeName = (otherNode.title || "").toUpperCase();
11
+ let otherSlotType = otherSlot.type;
12
+ if (Array.isArray(otherSlotType) || otherSlotType.includes(",")) {
13
+ otherSlotType = "COMBO";
14
+ }
15
+ const otherSlotName = otherSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
16
+ let ctxSlotIndex = -1;
17
+ if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) {
18
+ ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => {
19
+ const ctxSlotName = ctxSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
20
+ let ctxSlotType = ctxSlot.type;
21
+ if (Array.isArray(ctxSlotType) || ctxSlotType.includes(",")) {
22
+ ctxSlotType = "COMBO";
23
+ }
24
+ if (ctxSlotType !== otherSlotType) {
25
+ return false;
26
+ }
27
+ if (ctxSlotName === otherSlotName ||
28
+ (ctxSlotName === "SEED" && otherSlotName.includes("SEED")) ||
29
+ (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP")) ||
30
+ (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) {
31
+ return true;
32
+ }
33
+ if ((otherNodeType.includes("POSITIVE") || otherNodeName.includes("POSITIVE")) &&
34
+ ((ctxSlotName === "POSITIVE" && otherSlotType === "CONDITIONING") ||
35
+ (ctxSlotName === "TEXT_POS_G" && otherSlotName.includes("TEXT_G")) ||
36
+ (ctxSlotName === "TEXT_POS_L" && otherSlotName.includes("TEXT_L")))) {
37
+ return true;
38
+ }
39
+ if ((otherNodeType.includes("NEGATIVE") || otherNodeName.includes("NEGATIVE")) &&
40
+ ((ctxSlotName === "NEGATIVE" && otherSlotType === "CONDITIONING") ||
41
+ (ctxSlotName === "TEXT_NEG_G" && otherSlotName.includes("TEXT_G")) ||
42
+ (ctxSlotName === "TEXT_NEG_L" && otherSlotName.includes("TEXT_L")))) {
43
+ return true;
44
+ }
45
+ return false;
46
+ });
47
+ }
48
+ else {
49
+ ctxSlotIndex = ctxSlots.map((s) => s.type).indexOf(otherSlotType);
50
+ }
51
+ return ctxSlotIndex;
52
+ }
53
+ export class BaseContextNode extends RgthreeBaseServerNode {
54
+ constructor(title) {
55
+ super(title);
56
+ this.___collapsed_width = 0;
57
+ }
58
+ get _collapsed_width() {
59
+ return this.___collapsed_width;
60
+ }
61
+ set _collapsed_width(width) {
62
+ const canvas = app.canvas;
63
+ const ctx = canvas.canvas.getContext("2d");
64
+ const oldFont = ctx.font;
65
+ ctx.font = canvas.title_text_font;
66
+ let title = this.title.trim();
67
+ this.___collapsed_width = 30 + (title ? 10 + ctx.measureText(title).width : 0);
68
+ ctx.font = oldFont;
69
+ }
70
+ connectByType(slot, sourceNode, sourceSlotType, optsIn) {
71
+ let canConnect = super.connectByType &&
72
+ super.connectByType.call(this, slot, sourceNode, sourceSlotType, optsIn);
73
+ if (!super.connectByType) {
74
+ canConnect = LGraphNode.prototype.connectByType.call(this, slot, sourceNode, sourceSlotType, optsIn);
75
+ }
76
+ if (!canConnect && slot === 0) {
77
+ const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
78
+ for (const [index, input] of (sourceNode.inputs || []).entries()) {
79
+ if (input.link && !ctrlKey) {
80
+ continue;
81
+ }
82
+ const thisOutputSlot = findMatchingIndexByTypeOrName(sourceNode, input, this.outputs);
83
+ if (thisOutputSlot > -1) {
84
+ this.connect(thisOutputSlot, sourceNode, index);
85
+ }
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
91
+ var _a;
92
+ let canConnect = super.connectByTypeOutput &&
93
+ super.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn);
94
+ if (!super.connectByType) {
95
+ canConnect = LGraphNode.prototype.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn);
96
+ }
97
+ if (!canConnect && slot === 0) {
98
+ const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
99
+ for (const [index, output] of (sourceNode.outputs || []).entries()) {
100
+ if (((_a = output.links) === null || _a === void 0 ? void 0 : _a.length) && !ctrlKey) {
101
+ continue;
102
+ }
103
+ const thisInputSlot = findMatchingIndexByTypeOrName(sourceNode, output, this.inputs);
104
+ if (thisInputSlot > -1) {
105
+ sourceNode.connect(index, this, thisInputSlot);
106
+ }
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+ static setUp(comfyClass, nodeData, ctxClass) {
112
+ RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ctxClass);
113
+ wait(500).then(() => {
114
+ LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] =
115
+ LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] || [];
116
+ LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"].push(comfyClass.comfyClass);
117
+ });
118
+ }
119
+ static onRegisteredForOverride(comfyClass, ctxClass) {
120
+ addConnectionLayoutSupport(ctxClass, app, [
121
+ ["Left", "Right"],
122
+ ["Right", "Left"],
123
+ ]);
124
+ setTimeout(() => {
125
+ ctxClass.category = comfyClass.category;
126
+ });
127
+ }
128
+ }
129
+ class ContextNode extends BaseContextNode {
130
+ constructor(title = ContextNode.title) {
131
+ super(title);
132
+ }
133
+ static setUp(comfyClass, nodeData) {
134
+ BaseContextNode.setUp(comfyClass, nodeData, ContextNode);
135
+ }
136
+ static onRegisteredForOverride(comfyClass, ctxClass) {
137
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
138
+ addMenuItem(ContextNode, app, {
139
+ name: "Convert To Context Big",
140
+ callback: (node) => {
141
+ replaceNode(node, ContextBigNode.type);
142
+ },
143
+ });
144
+ }
145
+ }
146
+ ContextNode.title = NodeTypesString.CONTEXT;
147
+ ContextNode.type = NodeTypesString.CONTEXT;
148
+ ContextNode.comfyClass = NodeTypesString.CONTEXT;
149
+ class ContextBigNode extends BaseContextNode {
150
+ constructor(title = ContextBigNode.title) {
151
+ super(title);
152
+ }
153
+ static setUp(comfyClass, nodeData) {
154
+ BaseContextNode.setUp(comfyClass, nodeData, ContextBigNode);
155
+ }
156
+ static onRegisteredForOverride(comfyClass, ctxClass) {
157
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
158
+ addMenuItem(ContextBigNode, app, {
159
+ name: "Convert To Context (Original)",
160
+ callback: (node) => {
161
+ replaceNode(node, ContextNode.type);
162
+ },
163
+ });
164
+ }
165
+ }
166
+ ContextBigNode.title = NodeTypesString.CONTEXT_BIG;
167
+ ContextBigNode.type = NodeTypesString.CONTEXT_BIG;
168
+ ContextBigNode.comfyClass = NodeTypesString.CONTEXT_BIG;
169
+ class BaseContextMultiCtxInputNode extends BaseContextNode {
170
+ constructor(title) {
171
+ super(title);
172
+ this.stabilizeBound = this.stabilize.bind(this);
173
+ this.addContextInput(5);
174
+ }
175
+ addContextInput(num = 1) {
176
+ for (let i = 0; i < num; i++) {
177
+ this.addInput(`ctx_${String(this.inputs.length + 1).padStart(2, "0")}`, "RGTHREE_CONTEXT");
178
+ }
179
+ }
180
+ onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
181
+ var _a;
182
+ (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
183
+ if (type === LiteGraph.INPUT) {
184
+ this.scheduleStabilize();
185
+ }
186
+ }
187
+ scheduleStabilize(ms = 64) {
188
+ return debounce(this.stabilizeBound, 64);
189
+ }
190
+ stabilize() {
191
+ removeUnusedInputsFromEnd(this, 4);
192
+ this.addContextInput();
193
+ }
194
+ }
195
+ class ContextSwitchNode extends BaseContextMultiCtxInputNode {
196
+ constructor(title = ContextSwitchNode.title) {
197
+ super(title);
198
+ }
199
+ static setUp(comfyClass, nodeData) {
200
+ BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchNode);
201
+ }
202
+ static onRegisteredForOverride(comfyClass, ctxClass) {
203
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
204
+ addMenuItem(ContextSwitchNode, app, {
205
+ name: "Convert To Context Switch Big",
206
+ callback: (node) => {
207
+ replaceNode(node, ContextSwitchBigNode.type);
208
+ },
209
+ });
210
+ }
211
+ }
212
+ ContextSwitchNode.title = NodeTypesString.CONTEXT_SWITCH;
213
+ ContextSwitchNode.type = NodeTypesString.CONTEXT_SWITCH;
214
+ ContextSwitchNode.comfyClass = NodeTypesString.CONTEXT_SWITCH;
215
+ class ContextSwitchBigNode extends BaseContextMultiCtxInputNode {
216
+ constructor(title = ContextSwitchBigNode.title) {
217
+ super(title);
218
+ }
219
+ static setUp(comfyClass, nodeData) {
220
+ BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchBigNode);
221
+ }
222
+ static onRegisteredForOverride(comfyClass, ctxClass) {
223
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
224
+ addMenuItem(ContextSwitchBigNode, app, {
225
+ name: "Convert To Context Switch",
226
+ callback: (node) => {
227
+ replaceNode(node, ContextSwitchNode.type);
228
+ },
229
+ });
230
+ }
231
+ }
232
+ ContextSwitchBigNode.title = NodeTypesString.CONTEXT_SWITCH_BIG;
233
+ ContextSwitchBigNode.type = NodeTypesString.CONTEXT_SWITCH_BIG;
234
+ ContextSwitchBigNode.comfyClass = NodeTypesString.CONTEXT_SWITCH_BIG;
235
+ class ContextMergeNode extends BaseContextMultiCtxInputNode {
236
+ constructor(title = ContextMergeNode.title) {
237
+ super(title);
238
+ }
239
+ static setUp(comfyClass, nodeData) {
240
+ BaseContextNode.setUp(comfyClass, nodeData, ContextMergeNode);
241
+ }
242
+ static onRegisteredForOverride(comfyClass, ctxClass) {
243
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
244
+ addMenuItem(ContextMergeNode, app, {
245
+ name: "Convert To Context Merge Big",
246
+ callback: (node) => {
247
+ replaceNode(node, ContextMergeBigNode.type);
248
+ },
249
+ });
250
+ }
251
+ }
252
+ ContextMergeNode.title = NodeTypesString.CONTEXT_MERGE;
253
+ ContextMergeNode.type = NodeTypesString.CONTEXT_MERGE;
254
+ ContextMergeNode.comfyClass = NodeTypesString.CONTEXT_MERGE;
255
+ class ContextMergeBigNode extends BaseContextMultiCtxInputNode {
256
+ constructor(title = ContextMergeBigNode.title) {
257
+ super(title);
258
+ }
259
+ static setUp(comfyClass, nodeData) {
260
+ BaseContextNode.setUp(comfyClass, nodeData, ContextMergeBigNode);
261
+ }
262
+ static onRegisteredForOverride(comfyClass, ctxClass) {
263
+ BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
264
+ addMenuItem(ContextMergeBigNode, app, {
265
+ name: "Convert To Context Switch",
266
+ callback: (node) => {
267
+ replaceNode(node, ContextMergeNode.type);
268
+ },
269
+ });
270
+ }
271
+ }
272
+ ContextMergeBigNode.title = NodeTypesString.CONTEXT_MERGE_BIG;
273
+ ContextMergeBigNode.type = NodeTypesString.CONTEXT_MERGE_BIG;
274
+ ContextMergeBigNode.comfyClass = NodeTypesString.CONTEXT_MERGE_BIG;
275
+ const contextNodes = [
276
+ ContextNode,
277
+ ContextBigNode,
278
+ ContextSwitchNode,
279
+ ContextSwitchBigNode,
280
+ ContextMergeNode,
281
+ ContextMergeBigNode,
282
+ ];
283
+ const contextTypeToServerDef = {};
284
+ function fixBadConfigs(node) {
285
+ const wrongName = node.outputs.find((o, i) => o.name === "CLIP_HEIGTH");
286
+ if (wrongName) {
287
+ wrongName.name = "CLIP_HEIGHT";
288
+ }
289
+ }
290
+ app.registerExtension({
291
+ name: "rgthree.Context",
292
+ async beforeRegisterNodeDef(nodeType, nodeData) {
293
+ for (const ctxClass of contextNodes) {
294
+ if (nodeData.name === ctxClass.type) {
295
+ contextTypeToServerDef[ctxClass.type] = nodeData;
296
+ ctxClass.setUp(nodeType, nodeData);
297
+ break;
298
+ }
299
+ }
300
+ },
301
+ async nodeCreated(node) {
302
+ const type = node.type || node.constructor.type;
303
+ const serverDef = type && contextTypeToServerDef[type];
304
+ if (serverDef) {
305
+ fixBadConfigs(node);
306
+ matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
307
+ if (!type.includes("Switch") && !type.includes("Merge")) {
308
+ matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
309
+ }
310
+ }
311
+ },
312
+ async loadedGraphNode(node) {
313
+ const type = node.type || node.constructor.type;
314
+ const serverDef = type && contextTypeToServerDef[type];
315
+ if (serverDef) {
316
+ fixBadConfigs(node);
317
+ matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
318
+ if (!type.includes("Switch") && !type.includes("Merge")) {
319
+ matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
320
+ }
321
+ }
322
+ },
323
+ });
rgthree-comfy/web/comfyui/dialog_info.js ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { RgthreeDialog } from "../../rgthree/common/dialog.js";
2
+ import { createElement as $el, empty, appendChildren, getClosestOrSelf, queryOne, query, setAttributes, } from "../../rgthree/common/utils_dom.js";
3
+ import { logoCivitai, link, pencilColored, diskColored, dotdotdot, } from "../../rgthree/common/media/svgs.js";
4
+ import { LORA_INFO_SERVICE } from "../../rgthree/common/model_info_service.js";
5
+ import { rgthree } from "./rgthree.js";
6
+ import { MenuButton } from "../../rgthree/common/menu.js";
7
+ import { generateId, injectCss } from "../../rgthree/common/shared_utils.js";
8
+ class RgthreeInfoDialog extends RgthreeDialog {
9
+ constructor(file, type = "lora") {
10
+ const dialogOptions = {
11
+ class: "rgthree-info-dialog",
12
+ title: `<h2>Loading...</h2>`,
13
+ content: "<center>Loading..</center>",
14
+ onBeforeClose: () => {
15
+ return true;
16
+ },
17
+ };
18
+ super(dialogOptions);
19
+ this.modifiedModelData = false;
20
+ this.modelInfo = null;
21
+ this.init(file);
22
+ }
23
+ async init(file) {
24
+ var _a, _b;
25
+ const cssPromise = injectCss("rgthree/common/css/dialog_model_info.css");
26
+ this.modelInfo = await this.getModelInfo(file);
27
+ await cssPromise;
28
+ this.setContent(this.getInfoContent());
29
+ this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
30
+ this.attachEvents();
31
+ }
32
+ getCloseEventDetail() {
33
+ const detail = {
34
+ dirty: this.modifiedModelData,
35
+ };
36
+ return { detail };
37
+ }
38
+ attachEvents() {
39
+ this.contentElement.addEventListener("click", async (e) => {
40
+ const target = getClosestOrSelf(e.target, "[data-action]");
41
+ const action = target === null || target === void 0 ? void 0 : target.getAttribute("data-action");
42
+ if (!target || !action) {
43
+ return;
44
+ }
45
+ await this.handleEventAction(action, target, e);
46
+ });
47
+ }
48
+ async handleEventAction(action, target, e) {
49
+ var _a, _b;
50
+ const info = this.modelInfo;
51
+ if (!(info === null || info === void 0 ? void 0 : info.file)) {
52
+ return;
53
+ }
54
+ if (action === "fetch-civitai") {
55
+ this.modelInfo = await this.refreshModelInfo(info.file);
56
+ this.setContent(this.getInfoContent());
57
+ this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
58
+ }
59
+ else if (action === "copy-trained-words") {
60
+ const selected = query(".-rgthree-is-selected", target.closest("tr"));
61
+ const text = selected.map((el) => el.getAttribute("data-word")).join(", ");
62
+ await navigator.clipboard.writeText(text);
63
+ rgthree.showMessage({
64
+ id: "copy-trained-words-" + generateId(4),
65
+ type: "success",
66
+ message: `Successfully copied ${selected.length} key word${selected.length === 1 ? "" : "s"}.`,
67
+ timeout: 4000,
68
+ });
69
+ }
70
+ else if (action === "toggle-trained-word") {
71
+ target === null || target === void 0 ? void 0 : target.classList.toggle("-rgthree-is-selected");
72
+ const tr = target.closest("tr");
73
+ if (tr) {
74
+ const span = queryOne("td:first-child > *", tr);
75
+ let small = queryOne("small", span);
76
+ if (!small) {
77
+ small = $el("small", { parent: span });
78
+ }
79
+ const num = query(".-rgthree-is-selected", tr).length;
80
+ small.innerHTML = num
81
+ ? `${num} selected | <span role="button" data-action="copy-trained-words">Copy</span>`
82
+ : "";
83
+ }
84
+ }
85
+ else if (action === "edit-row") {
86
+ const tr = target.closest("tr");
87
+ const td = queryOne("td:nth-child(2)", tr);
88
+ const input = td.querySelector("input,textarea");
89
+ if (!input) {
90
+ const fieldName = tr.dataset["fieldName"];
91
+ tr.classList.add("-rgthree-editing");
92
+ const isTextarea = fieldName === "userNote";
93
+ const input = $el(`${isTextarea ? "textarea" : 'input[type="text"]'}`, {
94
+ value: td.textContent,
95
+ });
96
+ input.addEventListener("keydown", (e) => {
97
+ if (!isTextarea && e.key === "Enter") {
98
+ const modified = saveEditableRow(info, tr, true);
99
+ this.modifiedModelData = this.modifiedModelData || modified;
100
+ e.stopPropagation();
101
+ e.preventDefault();
102
+ }
103
+ else if (e.key === "Escape") {
104
+ const modified = saveEditableRow(info, tr, false);
105
+ this.modifiedModelData = this.modifiedModelData || modified;
106
+ e.stopPropagation();
107
+ e.preventDefault();
108
+ }
109
+ });
110
+ appendChildren(empty(td), [input]);
111
+ input.focus();
112
+ }
113
+ else if (target.nodeName.toLowerCase() === "button") {
114
+ const modified = saveEditableRow(info, tr, true);
115
+ this.modifiedModelData = this.modifiedModelData || modified;
116
+ }
117
+ e === null || e === void 0 ? void 0 : e.preventDefault();
118
+ e === null || e === void 0 ? void 0 : e.stopPropagation();
119
+ }
120
+ }
121
+ getInfoContent() {
122
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
123
+ const info = this.modelInfo || {};
124
+ const civitaiLink = (_a = info.links) === null || _a === void 0 ? void 0 : _a.find((i) => i.includes("civitai.com/models"));
125
+ const html = `
126
+ <ul class="rgthree-info-area">
127
+ <li title="Type" class="rgthree-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${info.type || ""}</span></li>
128
+ <li title="Base Model" class="rgthree-info-tag -basemodel -basemodel-${(info.baseModel || "").toLowerCase()}"><span>${info.baseModel || ""}</span></li>
129
+ <li class="rgthree-info-menu" stub="menu"></li>
130
+ ${""}
131
+ </ul>
132
+
133
+ <table class="rgthree-info-table">
134
+ ${infoTableRow("File", info.file || "")}
135
+ ${infoTableRow("Hash (sha256)", info.sha256 || "")}
136
+ ${civitaiLink
137
+ ? infoTableRow("Civitai", `<a href="${civitaiLink}" target="_blank">${logoCivitai}View on Civitai</a>`)
138
+ : ((_c = (_b = info.raw) === null || _b === void 0 ? void 0 : _b.civitai) === null || _c === void 0 ? void 0 : _c.error) === "Model not found"
139
+ ? infoTableRow("Civitai", '<i>Model not found</i> <span class="-help" title="The model was not found on civitai with the sha256 hash. It\'s possible the model was removed, re-uploaded, or was never on civitai to begin with."></span>')
140
+ : ((_e = (_d = info.raw) === null || _d === void 0 ? void 0 : _d.civitai) === null || _e === void 0 ? void 0 : _e.error)
141
+ ? infoTableRow("Civitai", (_g = (_f = info.raw) === null || _f === void 0 ? void 0 : _f.civitai) === null || _g === void 0 ? void 0 : _g.error)
142
+ : !((_h = info.raw) === null || _h === void 0 ? void 0 : _h.civitai)
143
+ ? infoTableRow("Civitai", `<button class="rgthree-button" data-action="fetch-civitai">Fetch info from civitai</button>`)
144
+ : ""}
145
+
146
+ ${infoTableRow("Name", info.name || ((_k = (_j = info.raw) === null || _j === void 0 ? void 0 : _j.metadata) === null || _k === void 0 ? void 0 : _k.ss_output_name) || "", "The name for display.", "name")}
147
+
148
+ ${!info.baseModelFile && !info.baseModelFile
149
+ ? ""
150
+ : infoTableRow("Base Model", (info.baseModel || "") + (info.baseModelFile ? ` (${info.baseModelFile})` : ""))}
151
+
152
+
153
+ ${!((_l = info.trainedWords) === null || _l === void 0 ? void 0 : _l.length)
154
+ ? ""
155
+ : infoTableRow("Trained Words", (_m = getTrainedWordsMarkup(info.trainedWords)) !== null && _m !== void 0 ? _m : "", "Trained words from the metadata and/or civitai. Click to select for copy.")}
156
+
157
+ ${!((_p = (_o = info.raw) === null || _o === void 0 ? void 0 : _o.metadata) === null || _p === void 0 ? void 0 : _p.ss_clip_skip) || ((_r = (_q = info.raw) === null || _q === void 0 ? void 0 : _q.metadata) === null || _r === void 0 ? void 0 : _r.ss_clip_skip) == "None"
158
+ ? ""
159
+ : infoTableRow("Clip Skip", (_t = (_s = info.raw) === null || _s === void 0 ? void 0 : _s.metadata) === null || _t === void 0 ? void 0 : _t.ss_clip_skip)}
160
+ ${infoTableRow("Strength Min", (_u = info.strengthMin) !== null && _u !== void 0 ? _u : "", "The recommended minimum strength, In the Power Lora Loader node, strength will signal when it is below this threshold.", "strengthMin")}
161
+ ${infoTableRow("Strength Max", (_v = info.strengthMax) !== null && _v !== void 0 ? _v : "", "The recommended maximum strength. In the Power Lora Loader node, strength will signal when it is above this threshold.", "strengthMax")}
162
+ ${""}
163
+ ${infoTableRow("Additional Notes", (_w = info.userNote) !== null && _w !== void 0 ? _w : "", "Additional notes you'd like to keep and reference in the info dialog.", "userNote")}
164
+
165
+ </table>
166
+
167
+ <ul class="rgthree-info-images">${(_y = (_x = info.images) === null || _x === void 0 ? void 0 : _x.map((img) => `
168
+ <li>
169
+ <figure>
170
+ <img src="${img.url}" />
171
+ <figcaption><!--
172
+ -->${imgInfoField("", img.civitaiUrl
173
+ ? `<a href="${img.civitaiUrl}" target="_blank">civitai${link}</a>`
174
+ : undefined)}<!--
175
+ -->${imgInfoField("seed", img.seed)}<!--
176
+ -->${imgInfoField("steps", img.steps)}<!--
177
+ -->${imgInfoField("cfg", img.cfg)}<!--
178
+ -->${imgInfoField("sampler", img.sampler)}<!--
179
+ -->${imgInfoField("model", img.model)}<!--
180
+ -->${imgInfoField("positive", img.positive)}<!--
181
+ -->${imgInfoField("negative", img.negative)}<!--
182
+ --><!--${""}--></figcaption>
183
+ </figure>
184
+ </li>`).join("")) !== null && _y !== void 0 ? _y : ""}</ul>
185
+ `;
186
+ const div = $el("div", { html });
187
+ if (rgthree.isDevMode()) {
188
+ setAttributes(queryOne('[stub="menu"]', div), {
189
+ children: [
190
+ new MenuButton({
191
+ icon: dotdotdot,
192
+ options: [
193
+ { label: "More Actions", type: "title" },
194
+ {
195
+ label: "Open API JSON",
196
+ callback: async (e) => {
197
+ var _a;
198
+ if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
199
+ window.open(`rgthree/api/loras/info?file=${encodeURIComponent(this.modelInfo.file)}`);
200
+ }
201
+ },
202
+ },
203
+ {
204
+ label: "Clear all local info",
205
+ callback: async (e) => {
206
+ var _a, _b, _c;
207
+ if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
208
+ this.modelInfo = await LORA_INFO_SERVICE.clearFetchedInfo(this.modelInfo.file);
209
+ this.setContent(this.getInfoContent());
210
+ this.setTitle(((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["name"]) || ((_c = this.modelInfo) === null || _c === void 0 ? void 0 : _c["file"]) || "Unknown");
211
+ }
212
+ },
213
+ },
214
+ ],
215
+ }),
216
+ ],
217
+ });
218
+ }
219
+ return div;
220
+ }
221
+ }
222
+ export class RgthreeLoraInfoDialog extends RgthreeInfoDialog {
223
+ async getModelInfo(file) {
224
+ return LORA_INFO_SERVICE.getInfo(file, false, false);
225
+ }
226
+ async refreshModelInfo(file) {
227
+ return LORA_INFO_SERVICE.refreshInfo(file);
228
+ }
229
+ async clearModelInfo(file) {
230
+ return LORA_INFO_SERVICE.clearFetchedInfo(file);
231
+ }
232
+ }
233
+ function infoTableRow(name, value, help = "", editableFieldName = "") {
234
+ return `
235
+ <tr class="${editableFieldName ? "editable" : ""}" ${editableFieldName ? `data-field-name="${editableFieldName}"` : ""}>
236
+ <td><span>${name} ${help ? `<span class="-help" title="${help}"></span>` : ""}<span></td>
237
+ <td ${editableFieldName ? "" : 'colspan="2"'}>${String(value).startsWith("<") ? value : `<span>${value}<span>`}</td>
238
+ ${editableFieldName
239
+ ? `<td style="width: 24px;"><button class="rgthree-button-reset rgthree-button-edit" data-action="edit-row">${pencilColored}${diskColored}</button></td>`
240
+ : ""}
241
+ </tr>`;
242
+ }
243
+ function getTrainedWordsMarkup(words) {
244
+ let markup = `<ul class="rgthree-info-trained-words-list">`;
245
+ for (const wordData of words || []) {
246
+ markup += `<li title="${wordData.word}" data-word="${wordData.word}" class="rgthree-info-trained-words-list-item" data-action="toggle-trained-word">
247
+ <span>${wordData.word}</span>
248
+ ${wordData.civitai ? logoCivitai : ""}
249
+ ${wordData.count != null ? `<small>${wordData.count}</small>` : ""}
250
+ </li>`;
251
+ }
252
+ markup += `</ul>`;
253
+ return markup;
254
+ }
255
+ function saveEditableRow(info, tr, saving = true) {
256
+ var _a;
257
+ const fieldName = tr.dataset["fieldName"];
258
+ const input = queryOne("input,textarea", tr);
259
+ let newValue = (_a = info[fieldName]) !== null && _a !== void 0 ? _a : "";
260
+ let modified = false;
261
+ if (saving) {
262
+ newValue = input.value;
263
+ if (fieldName.startsWith("strength")) {
264
+ if (Number.isNaN(Number(newValue))) {
265
+ alert(`You must enter a number into the ${fieldName} field.`);
266
+ return false;
267
+ }
268
+ newValue = (Math.round(Number(newValue) * 100) / 100).toFixed(2);
269
+ }
270
+ LORA_INFO_SERVICE.savePartialInfo(info.file, { [fieldName]: newValue });
271
+ modified = true;
272
+ }
273
+ tr.classList.remove("-rgthree-editing");
274
+ const td = queryOne("td:nth-child(2)", tr);
275
+ appendChildren(empty(td), [$el("span", { text: newValue })]);
276
+ return modified;
277
+ }
278
+ function imgInfoField(label, value) {
279
+ return value != null ? `<span>${label ? `<label>${label} </label>` : ""}${value}</span>` : "";
280
+ }
rgthree-comfy/web/comfyui/display_any.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { ComfyWidgets } from "../../scripts/widgets.js";
3
+ import { addConnectionLayoutSupport } from "./utils.js";
4
+ import { rgthree } from "./rgthree.js";
5
+ let hasShownAlertForUpdatingInt = false;
6
+ app.registerExtension({
7
+ name: "rgthree.DisplayAny",
8
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
9
+ if (nodeData.name === "Display Any (rgthree)" || nodeData.name === "Display Int (rgthree)") {
10
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
11
+ nodeType.prototype.onNodeCreated = function () {
12
+ onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
13
+ this.showValueWidget = ComfyWidgets["STRING"](this, "output", ["STRING", { multiline: true }], app).widget;
14
+ this.showValueWidget.inputEl.readOnly = true;
15
+ this.showValueWidget.serializeValue = async (node, index) => {
16
+ const n = rgthree.getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node);
17
+ if (n) {
18
+ n.widgets_values[index] = "";
19
+ }
20
+ else {
21
+ console.warn("No serialized node found in workflow. May be attributed to " +
22
+ "https://github.com/comfyanonymous/ComfyUI/issues/2193");
23
+ }
24
+ return "";
25
+ };
26
+ };
27
+ addConnectionLayoutSupport(nodeType, app, [["Left"], ["Right"]]);
28
+ const onExecuted = nodeType.prototype.onExecuted;
29
+ nodeType.prototype.onExecuted = function (message) {
30
+ onExecuted === null || onExecuted === void 0 ? void 0 : onExecuted.apply(this, [message]);
31
+ this.showValueWidget.value = message.text[0];
32
+ };
33
+ }
34
+ },
35
+ });
rgthree-comfy/web/comfyui/dynamic_context.js ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { IoDirection, followConnectionUntilType, getConnectedInputInfosAndFilterPassThroughs, } from "./utils.js";
3
+ import { rgthree } from "./rgthree.js";
4
+ import { SERVICE as CONTEXT_SERVICE, InputMutationOperation, } from "./services/context_service.js";
5
+ import { NodeTypesString } from "./constants.js";
6
+ import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
7
+ import { DynamicContextNodeBase } from "./dynamic_context_base.js";
8
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
9
+ const OWNED_PREFIX = "+";
10
+ const REGEX_OWNED_PREFIX = /^\+\s*/;
11
+ const REGEX_EMPTY_INPUT = /^\+\s*$/;
12
+ export class DynamicContextNode extends DynamicContextNodeBase {
13
+ constructor(title = DynamicContextNode.title) {
14
+ super(title);
15
+ }
16
+ onNodeCreated() {
17
+ this.addInput("base_ctx", "RGTHREE_DYNAMIC_CONTEXT");
18
+ this.ensureOneRemainingNewInputSlot();
19
+ super.onNodeCreated();
20
+ }
21
+ onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
22
+ var _a;
23
+ (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, ioSlot);
24
+ if (this.configuring) {
25
+ return;
26
+ }
27
+ if (type === LiteGraph.INPUT) {
28
+ if (isConnected) {
29
+ this.handleInputConnected(slotIndex);
30
+ }
31
+ else {
32
+ this.handleInputDisconnected(slotIndex);
33
+ }
34
+ }
35
+ }
36
+ onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
37
+ var _a;
38
+ let canConnect = true;
39
+ if (super.onConnectInput) {
40
+ canConnect = super.onConnectInput.apply(this, [...arguments]);
41
+ }
42
+ if (canConnect &&
43
+ outputNode instanceof DynamicContextNode &&
44
+ outputIndex === 0 &&
45
+ inputIndex !== 0) {
46
+ const [n, v] = rgthree.logger.warnParts("Currently, you can only connect a context node in the first slot.");
47
+ (_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
48
+ canConnect = false;
49
+ }
50
+ return canConnect;
51
+ }
52
+ handleInputConnected(slotIndex) {
53
+ const ioSlot = this.inputs[slotIndex];
54
+ const connectedIndexes = [];
55
+ if (slotIndex === 0) {
56
+ let baseNodeInfos = getConnectedInputInfosAndFilterPassThroughs(this, this, 0);
57
+ const baseNodes = baseNodeInfos.map((n) => n.node);
58
+ const baseNodesDynamicCtx = baseNodes[0];
59
+ if (baseNodesDynamicCtx === null || baseNodesDynamicCtx === void 0 ? void 0 : baseNodesDynamicCtx.provideInputsData) {
60
+ const inputsData = CONTEXT_SERVICE.getDynamicContextInputsData(baseNodesDynamicCtx);
61
+ console.log("inputsData", inputsData);
62
+ for (const input of baseNodesDynamicCtx.provideInputsData()) {
63
+ if (input.name === "base_ctx" || input.name === "+") {
64
+ continue;
65
+ }
66
+ this.addContextInput(input.name, input.type, input.index);
67
+ this.stabilizeNames();
68
+ }
69
+ }
70
+ }
71
+ else if (this.isInputSlotForNewInput(slotIndex)) {
72
+ this.handleNewInputConnected(slotIndex);
73
+ }
74
+ }
75
+ isInputSlotForNewInput(slotIndex) {
76
+ const ioSlot = this.inputs[slotIndex];
77
+ return ioSlot && ioSlot.name === "+" && ioSlot.type === "*";
78
+ }
79
+ handleNewInputConnected(slotIndex) {
80
+ if (!this.isInputSlotForNewInput(slotIndex)) {
81
+ throw new Error('Expected the incoming slot index to be the "new input" input.');
82
+ }
83
+ const ioSlot = this.inputs[slotIndex];
84
+ let cxn = null;
85
+ if (ioSlot.link != null) {
86
+ cxn = followConnectionUntilType(this, IoDirection.INPUT, slotIndex, true);
87
+ }
88
+ if ((cxn === null || cxn === void 0 ? void 0 : cxn.type) && (cxn === null || cxn === void 0 ? void 0 : cxn.name)) {
89
+ let name = this.addOwnedPrefix(this.getNextUniqueNameForThisNode(cxn.name));
90
+ if (name.match(/^\+\s*[A-Z_]+(\.\d+)?$/)) {
91
+ name = name.toLowerCase();
92
+ }
93
+ ioSlot.name = name;
94
+ ioSlot.type = cxn.type;
95
+ ioSlot.removable = true;
96
+ while (!this.outputs[slotIndex]) {
97
+ this.addOutput("*", "*");
98
+ }
99
+ this.outputs[slotIndex].type = cxn.type;
100
+ this.outputs[slotIndex].name = this.stripOwnedPrefix(name).toLocaleUpperCase();
101
+ if (cxn.type === "COMBO" || cxn.type.includes(",") || Array.isArray(cxn.type)) {
102
+ this.outputs[slotIndex].widget = true;
103
+ }
104
+ this.inputsMutated({
105
+ operation: InputMutationOperation.ADDED,
106
+ node: this,
107
+ slotIndex,
108
+ slot: ioSlot,
109
+ });
110
+ this.stabilizeNames();
111
+ this.ensureOneRemainingNewInputSlot();
112
+ }
113
+ }
114
+ handleInputDisconnected(slotIndex) {
115
+ var _a, _b;
116
+ const inputs = this.getContextInputsList();
117
+ if (slotIndex === 0) {
118
+ for (let index = inputs.length - 1; index > 0; index--) {
119
+ if (index === 0 || index === inputs.length - 1) {
120
+ continue;
121
+ }
122
+ const input = inputs[index];
123
+ if (!this.isOwnedInput(input.name)) {
124
+ if (input.link || ((_b = (_a = this.outputs[index]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length)) {
125
+ this.renameContextInput(index, input.name, true);
126
+ }
127
+ else {
128
+ this.removeContextInput(index);
129
+ }
130
+ }
131
+ }
132
+ this.setSize(this.computeSize());
133
+ this.setDirtyCanvas(true, true);
134
+ }
135
+ }
136
+ ensureOneRemainingNewInputSlot() {
137
+ removeUnusedInputsFromEnd(this, 1, REGEX_EMPTY_INPUT);
138
+ this.addInput(OWNED_PREFIX, "*");
139
+ }
140
+ getNextUniqueNameForThisNode(desiredName) {
141
+ const inputs = this.getContextInputsList();
142
+ const allExistingKeys = inputs.map((i) => this.stripOwnedPrefix(i.name).toLocaleUpperCase());
143
+ desiredName = this.stripOwnedPrefix(desiredName);
144
+ let newName = desiredName;
145
+ let n = 0;
146
+ while (allExistingKeys.includes(newName.toLocaleUpperCase())) {
147
+ newName = `${desiredName}.${++n}`;
148
+ }
149
+ return newName;
150
+ }
151
+ removeInput(slotIndex) {
152
+ const slot = this.inputs[slotIndex];
153
+ super.removeInput(slotIndex);
154
+ if (this.outputs[slotIndex]) {
155
+ this.removeOutput(slotIndex);
156
+ }
157
+ this.inputsMutated({ operation: InputMutationOperation.REMOVED, node: this, slotIndex, slot });
158
+ this.stabilizeNames();
159
+ }
160
+ stabilizeNames() {
161
+ const inputs = this.getContextInputsList();
162
+ const names = [];
163
+ for (const [index, input] of inputs.entries()) {
164
+ if (index === 0 || index === inputs.length - 1) {
165
+ continue;
166
+ }
167
+ input.label = undefined;
168
+ this.outputs[index].label = undefined;
169
+ let origName = this.stripOwnedPrefix(input.name).replace(/\.\d+$/, "");
170
+ let name = input.name;
171
+ if (!this.isOwnedInput(name)) {
172
+ names.push(name.toLocaleUpperCase());
173
+ }
174
+ else {
175
+ let n = 0;
176
+ name = this.addOwnedPrefix(origName);
177
+ while (names.includes(this.stripOwnedPrefix(name).toLocaleUpperCase())) {
178
+ name = `${this.addOwnedPrefix(origName)}.${++n}`;
179
+ }
180
+ names.push(this.stripOwnedPrefix(name).toLocaleUpperCase());
181
+ if (input.name !== name) {
182
+ this.renameContextInput(index, name);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ getSlotMenuOptions(slot) {
188
+ const editable = this.isOwnedInput(slot.input.name) && this.type !== "*";
189
+ return [
190
+ {
191
+ content: "✏️ Rename Input",
192
+ disabled: !editable,
193
+ callback: () => {
194
+ var dialog = app.canvas.createDialog("<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>", {});
195
+ var dialogInput = dialog.querySelector("input");
196
+ if (dialogInput) {
197
+ dialogInput.value = this.stripOwnedPrefix(slot.input.name || "");
198
+ }
199
+ var inner = () => {
200
+ this.handleContextMenuRenameInputDialog(slot.slot, dialogInput.value);
201
+ dialog.close();
202
+ };
203
+ dialog.querySelector("button").addEventListener("click", inner);
204
+ dialogInput.addEventListener("keydown", (e) => {
205
+ var _a;
206
+ dialog.is_modified = true;
207
+ if (e.keyCode == 27) {
208
+ dialog.close();
209
+ }
210
+ else if (e.keyCode == 13) {
211
+ inner();
212
+ }
213
+ else if (e.keyCode != 13 && ((_a = e.target) === null || _a === void 0 ? void 0 : _a.localName) != "textarea") {
214
+ return;
215
+ }
216
+ e.preventDefault();
217
+ e.stopPropagation();
218
+ });
219
+ dialogInput.focus();
220
+ },
221
+ },
222
+ {
223
+ content: "🗑️ Delete Input",
224
+ disabled: !editable,
225
+ callback: () => {
226
+ this.removeInput(slot.slot);
227
+ },
228
+ },
229
+ ];
230
+ }
231
+ handleContextMenuRenameInputDialog(slotIndex, value) {
232
+ app.graph.beforeChange();
233
+ this.renameContextInput(slotIndex, value);
234
+ this.stabilizeNames();
235
+ this.setDirtyCanvas(true, true);
236
+ app.graph.afterChange();
237
+ }
238
+ }
239
+ DynamicContextNode.title = NodeTypesString.DYNAMIC_CONTEXT;
240
+ DynamicContextNode.type = NodeTypesString.DYNAMIC_CONTEXT;
241
+ DynamicContextNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT;
242
+ const contextDynamicNodes = [DynamicContextNode];
243
+ app.registerExtension({
244
+ name: "rgthree.DynamicContext",
245
+ async beforeRegisterNodeDef(nodeType, nodeData) {
246
+ if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
247
+ return;
248
+ }
249
+ if (nodeData.name === DynamicContextNode.type) {
250
+ DynamicContextNode.setUp(nodeType, nodeData);
251
+ }
252
+ },
253
+ });
rgthree-comfy/web/comfyui/dynamic_context_base.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { BaseContextNode } from "./context.js";
2
+ import { RgthreeBaseServerNode } from "./base_node.js";
3
+ import { moveArrayItem, wait } from "../../rgthree/common/shared_utils.js";
4
+ import { RgthreeInvisibleWidget } from "./utils_widgets.js";
5
+ import { getContextOutputName, InputMutationOperation, } from "./services/context_service.js";
6
+ import { app } from "../../scripts/app.js";
7
+ import { SERVICE as CONTEXT_SERVICE } from "./services/context_service.js";
8
+ const OWNED_PREFIX = "+";
9
+ const REGEX_OWNED_PREFIX = /^\+\s*/;
10
+ const REGEX_EMPTY_INPUT = /^\+\s*$/;
11
+ export class DynamicContextNodeBase extends BaseContextNode {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.hasShadowInputs = false;
15
+ }
16
+ getContextInputsList() {
17
+ return this.inputs;
18
+ }
19
+ provideInputsData() {
20
+ const inputs = this.getContextInputsList();
21
+ return inputs
22
+ .map((input, index) => ({
23
+ name: this.stripOwnedPrefix(input.name),
24
+ type: String(input.type),
25
+ index,
26
+ }))
27
+ .filter((i) => i.type !== "*");
28
+ }
29
+ addOwnedPrefix(name) {
30
+ return `+ ${this.stripOwnedPrefix(name)}`;
31
+ }
32
+ isOwnedInput(inputOrName) {
33
+ const name = typeof inputOrName == "string" ? inputOrName : (inputOrName === null || inputOrName === void 0 ? void 0 : inputOrName.name) || "";
34
+ return REGEX_OWNED_PREFIX.test(name);
35
+ }
36
+ stripOwnedPrefix(name) {
37
+ return name.replace(REGEX_OWNED_PREFIX, "");
38
+ }
39
+ handleUpstreamMutation(mutation) {
40
+ console.log(`[node ${this.id}] handleUpstreamMutation`, mutation);
41
+ if (mutation.operation === InputMutationOperation.ADDED) {
42
+ const slot = mutation.slot;
43
+ if (!slot) {
44
+ throw new Error("Cannot have an ADDED mutation without a provided slot data.");
45
+ }
46
+ this.addContextInput(this.stripOwnedPrefix(slot.name), slot.type, mutation.slotIndex);
47
+ return;
48
+ }
49
+ if (mutation.operation === InputMutationOperation.REMOVED) {
50
+ const slot = mutation.slot;
51
+ if (!slot) {
52
+ throw new Error("Cannot have an REMOVED mutation without a provided slot data.");
53
+ }
54
+ this.removeContextInput(mutation.slotIndex);
55
+ return;
56
+ }
57
+ if (mutation.operation === InputMutationOperation.RENAMED) {
58
+ const slot = mutation.slot;
59
+ if (!slot) {
60
+ throw new Error("Cannot have an RENAMED mutation without a provided slot data.");
61
+ }
62
+ this.renameContextInput(mutation.slotIndex, slot.name);
63
+ return;
64
+ }
65
+ }
66
+ clone() {
67
+ const cloned = super.clone();
68
+ while (cloned.inputs.length > 1) {
69
+ cloned.removeInput(cloned.inputs.length - 1);
70
+ }
71
+ while (cloned.widgets.length > 1) {
72
+ cloned.removeWidget(cloned.widgets.length - 1);
73
+ }
74
+ while (cloned.outputs.length > 1) {
75
+ cloned.removeOutput(cloned.outputs.length - 1);
76
+ }
77
+ return cloned;
78
+ }
79
+ onNodeCreated() {
80
+ const node = this;
81
+ this.addCustomWidget(new RgthreeInvisibleWidget("output_keys", "RGTHREE_DYNAMIC_CONTEXT_OUTPUTS", "", () => {
82
+ return (node.outputs || [])
83
+ .map((o, i) => i > 0 && o.name)
84
+ .filter((n) => n !== false)
85
+ .join(",");
86
+ }));
87
+ }
88
+ addContextInput(name, type, slot = -1) {
89
+ const inputs = this.getContextInputsList();
90
+ if (this.hasShadowInputs) {
91
+ inputs.push({ name, type, link: null });
92
+ }
93
+ else {
94
+ this.addInput(name, type);
95
+ }
96
+ if (slot > -1) {
97
+ moveArrayItem(inputs, inputs.length - 1, slot);
98
+ }
99
+ else {
100
+ slot = inputs.length - 1;
101
+ }
102
+ if (type !== "*") {
103
+ const output = this.addOutput(getContextOutputName(name), type);
104
+ if (type === "COMBO" || String(type).includes(",") || Array.isArray(type)) {
105
+ output.widget = true;
106
+ }
107
+ if (slot > -1) {
108
+ moveArrayItem(this.outputs, this.outputs.length - 1, slot);
109
+ }
110
+ }
111
+ this.fixInputsOutputsLinkSlots();
112
+ this.inputsMutated({
113
+ operation: InputMutationOperation.ADDED,
114
+ node: this,
115
+ slotIndex: slot,
116
+ slot: inputs[slot],
117
+ });
118
+ }
119
+ removeContextInput(slotIndex) {
120
+ if (this.hasShadowInputs) {
121
+ const inputs = this.getContextInputsList();
122
+ const input = inputs.splice(slotIndex, 1)[0];
123
+ if (this.outputs[slotIndex]) {
124
+ this.removeOutput(slotIndex);
125
+ }
126
+ }
127
+ else {
128
+ this.removeInput(slotIndex);
129
+ }
130
+ }
131
+ renameContextInput(index, newName, forceOwnBool = null) {
132
+ const inputs = this.getContextInputsList();
133
+ const input = inputs[index];
134
+ const oldName = input.name;
135
+ newName = this.stripOwnedPrefix(newName.trim() || this.getSlotDefaultInputLabel(index));
136
+ if (forceOwnBool === true || (this.isOwnedInput(oldName) && forceOwnBool !== false)) {
137
+ newName = this.addOwnedPrefix(newName);
138
+ }
139
+ if (oldName !== newName) {
140
+ input.name = newName;
141
+ input.removable = this.isOwnedInput(newName);
142
+ this.outputs[index].name = getContextOutputName(inputs[index].name);
143
+ this.inputsMutated({
144
+ node: this,
145
+ operation: InputMutationOperation.RENAMED,
146
+ slotIndex: index,
147
+ slot: input,
148
+ });
149
+ }
150
+ }
151
+ getSlotDefaultInputLabel(slotIndex) {
152
+ const inputs = this.getContextInputsList();
153
+ const input = inputs[slotIndex];
154
+ let defaultLabel = this.stripOwnedPrefix(input.name).toLowerCase();
155
+ return defaultLabel.toLocaleLowerCase();
156
+ }
157
+ inputsMutated(mutation) {
158
+ CONTEXT_SERVICE.onInputChanges(this, mutation);
159
+ }
160
+ fixInputsOutputsLinkSlots() {
161
+ if (!this.hasShadowInputs) {
162
+ const inputs = this.getContextInputsList();
163
+ for (let index = inputs.length - 1; index > 0; index--) {
164
+ const input = inputs[index];
165
+ if ((input === null || input === void 0 ? void 0 : input.link) != null) {
166
+ app.graph.links[input.link].target_slot = index;
167
+ }
168
+ }
169
+ }
170
+ const outputs = this.outputs;
171
+ for (let index = outputs.length - 1; index > 0; index--) {
172
+ const output = outputs[index];
173
+ if (output) {
174
+ output.nameLocked = true;
175
+ for (const link of output.links || []) {
176
+ app.graph.links[link].origin_slot = index;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ static setUp(comfyClass, nodeData) {
182
+ RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, this);
183
+ wait(500).then(() => {
184
+ LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] =
185
+ LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] || [];
186
+ LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"].push(comfyClass.comfyClass);
187
+ });
188
+ }
189
+ }
rgthree-comfy/web/comfyui/dynamic_context_switch.js ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { DynamicContextNodeBase } from "./dynamic_context_base.js";
3
+ import { NodeTypesString } from "./constants.js";
4
+ import { SERVICE as CONTEXT_SERVICE, getContextOutputName, } from "./services/context_service.js";
5
+ import { getConnectedInputNodesAndFilterPassThroughs } from "./utils.js";
6
+ import { debounce, moveArrayItem } from "../../rgthree/common/shared_utils.js";
7
+ import { measureText } from "./utils_canvas.js";
8
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
9
+ class DynamicContextSwitchNode extends DynamicContextNodeBase {
10
+ constructor(title = DynamicContextSwitchNode.title) {
11
+ super(title);
12
+ this.hasShadowInputs = true;
13
+ this.lastInputsList = [];
14
+ this.shadowInputs = [
15
+ { name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0 },
16
+ ];
17
+ }
18
+ getContextInputsList() {
19
+ return this.shadowInputs;
20
+ }
21
+ handleUpstreamMutation(mutation) {
22
+ this.scheduleHardRefresh();
23
+ }
24
+ onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
25
+ var _a;
26
+ (_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, ioSlot);
27
+ if (this.configuring) {
28
+ return;
29
+ }
30
+ if (type === LiteGraph.INPUT) {
31
+ this.scheduleHardRefresh();
32
+ }
33
+ }
34
+ scheduleHardRefresh(ms = 64) {
35
+ return debounce(() => {
36
+ this.refreshInputsAndOutputs();
37
+ }, ms);
38
+ }
39
+ onNodeCreated() {
40
+ this.addInput("ctx_1", "RGTHREE_DYNAMIC_CONTEXT");
41
+ this.addInput("ctx_2", "RGTHREE_DYNAMIC_CONTEXT");
42
+ this.addInput("ctx_3", "RGTHREE_DYNAMIC_CONTEXT");
43
+ this.addInput("ctx_4", "RGTHREE_DYNAMIC_CONTEXT");
44
+ this.addInput("ctx_5", "RGTHREE_DYNAMIC_CONTEXT");
45
+ super.onNodeCreated();
46
+ }
47
+ addContextInput(name, type, slot) { }
48
+ refreshInputsAndOutputs() {
49
+ var _a;
50
+ const inputs = [
51
+ { name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0 },
52
+ ];
53
+ let numConnected = 0;
54
+ for (let i = 0; i < this.inputs.length; i++) {
55
+ const childCtxs = getConnectedInputNodesAndFilterPassThroughs(this, this, i);
56
+ if (childCtxs.length > 1) {
57
+ throw new Error("How is there more than one input?");
58
+ }
59
+ const ctx = childCtxs[0];
60
+ if (!ctx)
61
+ continue;
62
+ numConnected++;
63
+ const slotsData = CONTEXT_SERVICE.getDynamicContextInputsData(ctx);
64
+ console.log(slotsData);
65
+ for (const slotData of slotsData) {
66
+ const found = inputs.find((n) => getContextOutputName(slotData.name) === getContextOutputName(n.name));
67
+ if (found) {
68
+ found.count += 1;
69
+ continue;
70
+ }
71
+ inputs.push({
72
+ name: slotData.name,
73
+ type: slotData.type,
74
+ link: null,
75
+ count: 1,
76
+ });
77
+ }
78
+ }
79
+ this.shadowInputs = inputs;
80
+ let i = 0;
81
+ for (i; i < this.shadowInputs.length; i++) {
82
+ const data = this.shadowInputs[i];
83
+ let existing = this.outputs.find((o) => getContextOutputName(o.name) === getContextOutputName(data.name));
84
+ if (!existing) {
85
+ existing = this.addOutput(getContextOutputName(data.name), data.type);
86
+ }
87
+ moveArrayItem(this.outputs, existing, i);
88
+ delete existing.rgthree_status;
89
+ if (data.count !== numConnected) {
90
+ existing.rgthree_status = "WARN";
91
+ }
92
+ }
93
+ while (this.outputs[i]) {
94
+ const output = this.outputs[i];
95
+ if ((_a = output === null || output === void 0 ? void 0 : output.links) === null || _a === void 0 ? void 0 : _a.length) {
96
+ output.rgthree_status = "ERROR";
97
+ i++;
98
+ }
99
+ else {
100
+ this.removeOutput(i);
101
+ }
102
+ }
103
+ this.fixInputsOutputsLinkSlots();
104
+ }
105
+ onDrawForeground(ctx, canvas) {
106
+ var _a, _b;
107
+ const low_quality = ((_b = (_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1) < 0.6;
108
+ if (low_quality || this.size[0] <= 10) {
109
+ return;
110
+ }
111
+ let y = LiteGraph.NODE_SLOT_HEIGHT - 1;
112
+ const w = this.size[0];
113
+ ctx.save();
114
+ ctx.font = "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
115
+ ctx.textAlign = "right";
116
+ for (const output of this.outputs) {
117
+ if (!output.rgthree_status) {
118
+ y += LiteGraph.NODE_SLOT_HEIGHT;
119
+ continue;
120
+ }
121
+ const x = w - 20 - measureText(ctx, output.name);
122
+ if (output.rgthree_status === "ERROR") {
123
+ ctx.fillText("🛑", x, y);
124
+ }
125
+ else if (output.rgthree_status === "WARN") {
126
+ ctx.fillText("⚠️", x, y);
127
+ }
128
+ y += LiteGraph.NODE_SLOT_HEIGHT;
129
+ }
130
+ ctx.restore();
131
+ }
132
+ }
133
+ DynamicContextSwitchNode.title = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
134
+ DynamicContextSwitchNode.type = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
135
+ DynamicContextSwitchNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
136
+ app.registerExtension({
137
+ name: "rgthree.DynamicContextSwitch",
138
+ async beforeRegisterNodeDef(nodeType, nodeData) {
139
+ if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
140
+ return;
141
+ }
142
+ if (nodeData.name === DynamicContextSwitchNode.type) {
143
+ DynamicContextSwitchNode.setUp(nodeType, nodeData);
144
+ }
145
+ },
146
+ });
rgthree-comfy/web/comfyui/fast_actions_button.js ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
3
+ import { NodeTypesString } from "./constants.js";
4
+ import { addMenuItem } from "./utils.js";
5
+ import { rgthree } from "./rgthree.js";
6
+ const MODE_ALWAYS = 0;
7
+ const MODE_MUTE = 2;
8
+ const MODE_BYPASS = 4;
9
+ class FastActionsButton extends BaseAnyInputConnectedNode {
10
+ constructor(title) {
11
+ super(title);
12
+ this.comfyClass = NodeTypesString.FAST_ACTIONS_BUTTON;
13
+ this.logger = rgthree.newLogSession("[FastActionsButton]");
14
+ this.isVirtualNode = true;
15
+ this.serialize_widgets = true;
16
+ this.widgetToData = new Map();
17
+ this.nodeIdtoFunctionCache = new Map();
18
+ this.executingFromShortcut = false;
19
+ this.properties["buttonText"] = "🎬 Action!";
20
+ this.properties["shortcutModifier"] = "alt";
21
+ this.properties["shortcutKey"] = "";
22
+ this.buttonWidget = this.addWidget("button", this.properties["buttonText"], null, () => {
23
+ this.executeConnectedNodes();
24
+ }, { serialize: false });
25
+ this.keypressBound = this.onKeypress.bind(this);
26
+ this.keyupBound = this.onKeyup.bind(this);
27
+ this.onConstructed();
28
+ }
29
+ configure(info) {
30
+ super.configure(info);
31
+ setTimeout(() => {
32
+ if (info.widgets_values) {
33
+ for (let [index, value] of info.widgets_values.entries()) {
34
+ if (index > 0) {
35
+ if (value.startsWith("comfy_action:")) {
36
+ value = value.replace("comfy_action:", "");
37
+ this.addComfyActionWidget(index, value);
38
+ }
39
+ if (this.widgets[index]) {
40
+ this.widgets[index].value = value;
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }, 100);
46
+ }
47
+ clone() {
48
+ const cloned = super.clone();
49
+ cloned.properties["buttonText"] = "🎬 Action!";
50
+ cloned.properties["shortcutKey"] = "";
51
+ return cloned;
52
+ }
53
+ onAdded(graph) {
54
+ window.addEventListener("keydown", this.keypressBound);
55
+ window.addEventListener("keyup", this.keyupBound);
56
+ }
57
+ onRemoved() {
58
+ window.removeEventListener("keydown", this.keypressBound);
59
+ window.removeEventListener("keyup", this.keyupBound);
60
+ }
61
+ async onKeypress(event) {
62
+ const target = event.target;
63
+ if (this.executingFromShortcut ||
64
+ target.localName == "input" ||
65
+ target.localName == "textarea") {
66
+ return;
67
+ }
68
+ if (this.properties["shortcutKey"].trim() &&
69
+ this.properties["shortcutKey"].toLowerCase() === event.key.toLowerCase()) {
70
+ const shortcutModifier = this.properties["shortcutModifier"];
71
+ let good = shortcutModifier === "ctrl" && event.ctrlKey;
72
+ good = good || (shortcutModifier === "alt" && event.altKey);
73
+ good = good || (shortcutModifier === "shift" && event.shiftKey);
74
+ good = good || (shortcutModifier === "meta" && event.metaKey);
75
+ if (good) {
76
+ setTimeout(() => {
77
+ this.executeConnectedNodes();
78
+ }, 20);
79
+ this.executingFromShortcut = true;
80
+ event.preventDefault();
81
+ event.stopImmediatePropagation();
82
+ app.canvas.dirty_canvas = true;
83
+ return false;
84
+ }
85
+ }
86
+ return;
87
+ }
88
+ onKeyup(event) {
89
+ const target = event.target;
90
+ if (target.localName == "input" || target.localName == "textarea") {
91
+ return;
92
+ }
93
+ this.executingFromShortcut = false;
94
+ }
95
+ onPropertyChanged(property, value, _prevValue) {
96
+ if (property == "buttonText") {
97
+ this.buttonWidget.name = value;
98
+ }
99
+ if (property == "shortcutKey") {
100
+ value = value.trim();
101
+ this.properties["shortcutKey"] = (value && value[0].toLowerCase()) || "";
102
+ }
103
+ }
104
+ handleLinkedNodesStabilization(linkedNodes) {
105
+ var _a, _b, _c, _d, _e, _f, _g, _h;
106
+ let changed = false;
107
+ for (const [widget, data] of this.widgetToData.entries()) {
108
+ if (!data.node) {
109
+ continue;
110
+ }
111
+ if (!linkedNodes.includes(data.node)) {
112
+ const index = this.widgets.indexOf(widget);
113
+ if (index > -1) {
114
+ this.widgetToData.delete(widget);
115
+ this.removeWidget(widget);
116
+ changed = true;
117
+ }
118
+ else {
119
+ const [m, a] = this.logger.debugParts("Connected widget is not in widgets... weird.");
120
+ (_a = console[m]) === null || _a === void 0 ? void 0 : _a.call(console, ...a);
121
+ }
122
+ }
123
+ }
124
+ const badNodes = [];
125
+ let indexOffset = 1;
126
+ for (const [index, node] of linkedNodes.entries()) {
127
+ if (!node) {
128
+ const [m, a] = this.logger.debugParts("linkedNode provided that does not exist. ");
129
+ (_b = console[m]) === null || _b === void 0 ? void 0 : _b.call(console, ...a);
130
+ badNodes.push(node);
131
+ continue;
132
+ }
133
+ let widgetAtSlot = this.widgets[index + indexOffset];
134
+ if (widgetAtSlot && ((_c = this.widgetToData.get(widgetAtSlot)) === null || _c === void 0 ? void 0 : _c.comfy)) {
135
+ indexOffset++;
136
+ widgetAtSlot = this.widgets[index + indexOffset];
137
+ }
138
+ if (!widgetAtSlot || ((_e = (_d = this.widgetToData.get(widgetAtSlot)) === null || _d === void 0 ? void 0 : _d.node) === null || _e === void 0 ? void 0 : _e.id) !== node.id) {
139
+ let widget = null;
140
+ for (let i = index + indexOffset; i < this.widgets.length; i++) {
141
+ if (((_g = (_f = this.widgetToData.get(this.widgets[i])) === null || _f === void 0 ? void 0 : _f.node) === null || _g === void 0 ? void 0 : _g.id) === node.id) {
142
+ widget = this.widgets.splice(i, 1)[0];
143
+ this.widgets.splice(index + indexOffset, 0, widget);
144
+ changed = true;
145
+ break;
146
+ }
147
+ }
148
+ if (!widget) {
149
+ const exposedActions = node.constructor.exposedActions || [];
150
+ widget = this.addWidget("combo", node.title, "None", "", {
151
+ values: ["None", "Mute", "Bypass", "Enable", ...exposedActions],
152
+ });
153
+ widget.serializeValue = async (_node, _index) => {
154
+ return widget === null || widget === void 0 ? void 0 : widget.value;
155
+ };
156
+ this.widgetToData.set(widget, { node });
157
+ changed = true;
158
+ }
159
+ }
160
+ }
161
+ for (let i = this.widgets.length - 1; i > linkedNodes.length + indexOffset - 1; i--) {
162
+ const widgetAtSlot = this.widgets[i];
163
+ if (widgetAtSlot && ((_h = this.widgetToData.get(widgetAtSlot)) === null || _h === void 0 ? void 0 : _h.comfy)) {
164
+ continue;
165
+ }
166
+ this.removeWidget(widgetAtSlot);
167
+ changed = true;
168
+ }
169
+ return changed;
170
+ }
171
+ removeWidget(widgetOrSlot) {
172
+ const widget = typeof widgetOrSlot === "number" ? this.widgets[widgetOrSlot] : widgetOrSlot;
173
+ if (widget && this.widgetToData.has(widget)) {
174
+ this.widgetToData.delete(widget);
175
+ }
176
+ super.removeWidget(widgetOrSlot);
177
+ }
178
+ async executeConnectedNodes() {
179
+ var _a;
180
+ for (const widget of this.widgets) {
181
+ if (widget == this.buttonWidget) {
182
+ continue;
183
+ }
184
+ const action = widget.value;
185
+ const { comfy, node } = (_a = this.widgetToData.get(widget)) !== null && _a !== void 0 ? _a : {};
186
+ if (comfy) {
187
+ if (action === "Queue Prompt") {
188
+ await comfy.queuePrompt(0);
189
+ }
190
+ continue;
191
+ }
192
+ if (node) {
193
+ if (action === "Mute") {
194
+ node.mode = MODE_MUTE;
195
+ }
196
+ else if (action === "Bypass") {
197
+ node.mode = MODE_BYPASS;
198
+ }
199
+ else if (action === "Enable") {
200
+ node.mode = MODE_ALWAYS;
201
+ }
202
+ if (node.handleAction) {
203
+ await node.handleAction(action);
204
+ }
205
+ app.graph.change();
206
+ continue;
207
+ }
208
+ console.warn("Fast Actions Button has a widget without correct data.");
209
+ }
210
+ }
211
+ addComfyActionWidget(slot, value) {
212
+ let widget = this.addWidget("combo", "Comfy Action", "None", () => {
213
+ if (widget.value.startsWith("MOVE ")) {
214
+ this.widgets.push(this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
215
+ widget.value = widget["lastValue_"];
216
+ }
217
+ else if (widget.value.startsWith("REMOVE ")) {
218
+ this.removeWidget(widget);
219
+ }
220
+ widget["lastValue_"] = widget.value;
221
+ }, {
222
+ values: ["None", "Queue Prompt", "REMOVE Comfy Action", "MOVE to end"],
223
+ });
224
+ widget["lastValue_"] = value;
225
+ widget.serializeValue = async (_node, _index) => {
226
+ return `comfy_app:${widget === null || widget === void 0 ? void 0 : widget.value}`;
227
+ };
228
+ this.widgetToData.set(widget, { comfy: app });
229
+ if (slot != null) {
230
+ this.widgets.splice(slot, 0, this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
231
+ }
232
+ return widget;
233
+ }
234
+ onSerialize(o) {
235
+ var _a;
236
+ super.onSerialize && super.onSerialize(o);
237
+ for (let [index, value] of (o.widgets_values || []).entries()) {
238
+ if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "Comfy Action") {
239
+ o.widgets_values[index] = `comfy_action:${value}`;
240
+ }
241
+ }
242
+ }
243
+ static setUp() {
244
+ super.setUp();
245
+ addMenuItem(this, app, {
246
+ name: "➕ Append a Comfy Action",
247
+ callback: (nodeArg) => {
248
+ nodeArg.addComfyActionWidget();
249
+ },
250
+ });
251
+ }
252
+ }
253
+ FastActionsButton.type = NodeTypesString.FAST_ACTIONS_BUTTON;
254
+ FastActionsButton.title = NodeTypesString.FAST_ACTIONS_BUTTON;
255
+ FastActionsButton["@buttonText"] = { type: "string" };
256
+ FastActionsButton["@shortcutModifier"] = {
257
+ type: "combo",
258
+ values: ["ctrl", "alt", "shift"],
259
+ };
260
+ FastActionsButton["@shortcutKey"] = { type: "string" };
261
+ FastActionsButton.collapsible = false;
262
+ app.registerExtension({
263
+ name: "rgthree.FastActionsButton",
264
+ registerCustomNodes() {
265
+ FastActionsButton.setUp();
266
+ },
267
+ loadedGraphNode(node) {
268
+ if (node.type == FastActionsButton.title) {
269
+ node._tempWidth = node.size[0];
270
+ }
271
+ },
272
+ });
rgthree-comfy/web/comfyui/fast_groups_bypasser.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { NodeTypesString } from "./constants.js";
3
+ import { BaseFastGroupsModeChanger } from "./fast_groups_muter.js";
4
+ export class FastGroupsBypasser extends BaseFastGroupsModeChanger {
5
+ constructor(title = FastGroupsBypasser.title) {
6
+ super(title);
7
+ this.comfyClass = NodeTypesString.FAST_GROUPS_BYPASSER;
8
+ this.helpActions = "bypass and enable";
9
+ this.modeOn = LiteGraph.ALWAYS;
10
+ this.modeOff = 4;
11
+ this.onConstructed();
12
+ }
13
+ }
14
+ FastGroupsBypasser.type = NodeTypesString.FAST_GROUPS_BYPASSER;
15
+ FastGroupsBypasser.title = NodeTypesString.FAST_GROUPS_BYPASSER;
16
+ FastGroupsBypasser.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
17
+ app.registerExtension({
18
+ name: "rgthree.FastGroupsBypasser",
19
+ registerCustomNodes() {
20
+ FastGroupsBypasser.setUp();
21
+ },
22
+ loadedGraphNode(node) {
23
+ if (node.type == FastGroupsBypasser.title) {
24
+ node.tempSize = [...node.size];
25
+ }
26
+ },
27
+ });
rgthree-comfy/web/comfyui/fast_groups_muter.js ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { RgthreeBaseVirtualNode } from "./base_node.js";
3
+ import { NodeTypesString } from "./constants.js";
4
+ import { SERVICE as FAST_GROUPS_SERVICE } from "./services/fast_groups_service.js";
5
+ import { drawNodeWidget, fitString } from "./utils_canvas.js";
6
+ const PROPERTY_SORT = "sort";
7
+ const PROPERTY_SORT_CUSTOM_ALPHA = "customSortAlphabet";
8
+ const PROPERTY_MATCH_COLORS = "matchColors";
9
+ const PROPERTY_MATCH_TITLE = "matchTitle";
10
+ const PROPERTY_SHOW_NAV = "showNav";
11
+ const PROPERTY_RESTRICTION = "toggleRestriction";
12
+ export class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode {
13
+ constructor(title = FastGroupsMuter.title) {
14
+ super(title);
15
+ this.modeOn = LiteGraph.ALWAYS;
16
+ this.modeOff = LiteGraph.NEVER;
17
+ this.debouncerTempWidth = 0;
18
+ this.tempSize = null;
19
+ this.serialize_widgets = false;
20
+ this.helpActions = "mute and unmute";
21
+ this.properties[PROPERTY_MATCH_COLORS] = "";
22
+ this.properties[PROPERTY_MATCH_TITLE] = "";
23
+ this.properties[PROPERTY_SHOW_NAV] = true;
24
+ this.properties[PROPERTY_SORT] = "position";
25
+ this.properties[PROPERTY_SORT_CUSTOM_ALPHA] = "";
26
+ this.properties[PROPERTY_RESTRICTION] = "default";
27
+ }
28
+ onConstructed() {
29
+ this.addOutput("OPT_CONNECTION", "*");
30
+ return super.onConstructed();
31
+ }
32
+ configure(info) {
33
+ var _a;
34
+ if ((_a = info.outputs) === null || _a === void 0 ? void 0 : _a.length) {
35
+ info.outputs.length = 1;
36
+ }
37
+ super.configure(info);
38
+ }
39
+ onAdded(graph) {
40
+ FAST_GROUPS_SERVICE.addFastGroupNode(this);
41
+ }
42
+ onRemoved() {
43
+ FAST_GROUPS_SERVICE.removeFastGroupNode(this);
44
+ }
45
+ refreshWidgets() {
46
+ var _a, _b, _c, _d, _e, _f, _g, _h;
47
+ const canvas = app.canvas;
48
+ let sort = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SORT]) || "position";
49
+ let customAlphabet = null;
50
+ if (sort === "custom alphabet") {
51
+ const customAlphaStr = (_c = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_SORT_CUSTOM_ALPHA]) === null || _c === void 0 ? void 0 : _c.replace(/\n/g, "");
52
+ if (customAlphaStr && customAlphaStr.trim()) {
53
+ customAlphabet = customAlphaStr.includes(",")
54
+ ? customAlphaStr.toLocaleLowerCase().split(",")
55
+ : customAlphaStr.toLocaleLowerCase().trim().split("");
56
+ }
57
+ if (!(customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length)) {
58
+ sort = "alphanumeric";
59
+ customAlphabet = null;
60
+ }
61
+ }
62
+ const groups = [...FAST_GROUPS_SERVICE.getGroups(sort)];
63
+ if (customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length) {
64
+ groups.sort((a, b) => {
65
+ let aIndex = -1;
66
+ let bIndex = -1;
67
+ for (const [index, alpha] of customAlphabet.entries()) {
68
+ aIndex =
69
+ aIndex < 0 ? (a.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : aIndex;
70
+ bIndex =
71
+ bIndex < 0 ? (b.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : bIndex;
72
+ if (aIndex > -1 && bIndex > -1) {
73
+ break;
74
+ }
75
+ }
76
+ if (aIndex > -1 && bIndex > -1) {
77
+ const ret = aIndex - bIndex;
78
+ if (ret === 0) {
79
+ return a.title.localeCompare(b.title);
80
+ }
81
+ return ret;
82
+ }
83
+ else if (aIndex > -1) {
84
+ return -1;
85
+ }
86
+ else if (bIndex > -1) {
87
+ return 1;
88
+ }
89
+ return a.title.localeCompare(b.title);
90
+ });
91
+ }
92
+ let filterColors = (((_e = (_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_MATCH_COLORS]) === null || _e === void 0 ? void 0 : _e.split(",")) || []).filter((c) => c.trim());
93
+ if (filterColors.length) {
94
+ filterColors = filterColors.map((color) => {
95
+ color = color.trim().toLocaleLowerCase();
96
+ if (LGraphCanvas.node_colors[color]) {
97
+ color = LGraphCanvas.node_colors[color].groupcolor;
98
+ }
99
+ color = color.replace("#", "").toLocaleLowerCase();
100
+ if (color.length === 3) {
101
+ color = color.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
102
+ }
103
+ return `#${color}`;
104
+ });
105
+ }
106
+ let index = 0;
107
+ for (const group of groups) {
108
+ if (filterColors.length) {
109
+ let groupColor = (_f = group.color) === null || _f === void 0 ? void 0 : _f.replace("#", "").trim().toLocaleLowerCase();
110
+ if (!groupColor) {
111
+ continue;
112
+ }
113
+ if (groupColor.length === 3) {
114
+ groupColor = groupColor.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
115
+ }
116
+ groupColor = `#${groupColor}`;
117
+ if (!filterColors.includes(groupColor)) {
118
+ continue;
119
+ }
120
+ }
121
+ if ((_h = (_g = this.properties) === null || _g === void 0 ? void 0 : _g[PROPERTY_MATCH_TITLE]) === null || _h === void 0 ? void 0 : _h.trim()) {
122
+ try {
123
+ if (!new RegExp(this.properties[PROPERTY_MATCH_TITLE], "i").exec(group.title)) {
124
+ continue;
125
+ }
126
+ }
127
+ catch (e) {
128
+ console.error(e);
129
+ continue;
130
+ }
131
+ }
132
+ const widgetName = `Enable ${group.title}`;
133
+ let widget = this.widgets.find((w) => w.name === widgetName);
134
+ if (!widget) {
135
+ this.tempSize = [...this.size];
136
+ widget = this.addCustomWidget({
137
+ name: "RGTHREE_TOGGLE_AND_NAV",
138
+ label: "",
139
+ value: false,
140
+ disabled: false,
141
+ options: { on: "yes", off: "no" },
142
+ draw: function (ctx, node, width, posY, height) {
143
+ var _a;
144
+ const widgetData = drawNodeWidget(ctx, {
145
+ width,
146
+ height,
147
+ posY,
148
+ });
149
+ const showNav = ((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false;
150
+ let currentX = widgetData.width - widgetData.margin;
151
+ if (!widgetData.lowQuality && showNav) {
152
+ currentX -= 7;
153
+ const midY = widgetData.posY + widgetData.height * 0.5;
154
+ ctx.fillStyle = ctx.strokeStyle = "#89A";
155
+ ctx.lineJoin = "round";
156
+ ctx.lineCap = "round";
157
+ const arrow = new Path2D(`M${currentX} ${midY} l -7 6 v -3 h -7 v -6 h 7 v -3 z`);
158
+ ctx.fill(arrow);
159
+ ctx.stroke(arrow);
160
+ currentX -= 14;
161
+ currentX -= 7;
162
+ ctx.strokeStyle = widgetData.colorOutline;
163
+ ctx.stroke(new Path2D(`M ${currentX} ${widgetData.posY} v ${widgetData.height}`));
164
+ }
165
+ else if (widgetData.lowQuality && showNav) {
166
+ currentX -= 28;
167
+ }
168
+ currentX -= 7;
169
+ ctx.fillStyle = this.value ? "#89A" : "#333";
170
+ ctx.beginPath();
171
+ const toggleRadius = height * 0.36;
172
+ ctx.arc(currentX - toggleRadius, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
173
+ ctx.fill();
174
+ currentX -= toggleRadius * 2;
175
+ if (!widgetData.lowQuality) {
176
+ currentX -= 4;
177
+ ctx.textAlign = "right";
178
+ ctx.fillStyle = this.value ? widgetData.colorText : widgetData.colorTextSecondary;
179
+ const label = this.label || this.name;
180
+ const toggleLabelOn = this.options.on || "true";
181
+ const toggleLabelOff = this.options.off || "false";
182
+ ctx.fillText(this.value ? toggleLabelOn : toggleLabelOff, currentX, posY + height * 0.7);
183
+ currentX -= Math.max(ctx.measureText(toggleLabelOn).width, ctx.measureText(toggleLabelOff).width);
184
+ currentX -= 7;
185
+ ctx.textAlign = "left";
186
+ let maxLabelWidth = widgetData.width - widgetData.margin - 10 - (widgetData.width - currentX);
187
+ if (label != null) {
188
+ ctx.fillText(fitString(ctx, label, maxLabelWidth), widgetData.margin + 10, posY + height * 0.7);
189
+ }
190
+ }
191
+ },
192
+ serializeValue(serializedNode, widgetIndex) {
193
+ return this.value;
194
+ },
195
+ mouse(event, pos, node) {
196
+ var _a, _b, _c;
197
+ if (event.type == "pointerdown") {
198
+ if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false &&
199
+ pos[0] >= node.size[0] - 15 - 28 - 1) {
200
+ const canvas = app.canvas;
201
+ const lowQuality = (((_b = canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) || 1) <= 0.5;
202
+ if (!lowQuality) {
203
+ canvas.centerOnNode(group);
204
+ const zoomCurrent = ((_c = canvas.ds) === null || _c === void 0 ? void 0 : _c.scale) || 1;
205
+ const zoomX = canvas.canvas.width / group._size[0] - 0.02;
206
+ const zoomY = canvas.canvas.height / group._size[1] - 0.02;
207
+ canvas.setZoom(Math.min(zoomCurrent, zoomX, zoomY), [
208
+ canvas.canvas.width / 2,
209
+ canvas.canvas.height / 2,
210
+ ]);
211
+ canvas.setDirty(true, true);
212
+ }
213
+ }
214
+ else {
215
+ this.value = !this.value;
216
+ setTimeout(() => {
217
+ var _a;
218
+ (_a = this.callback) === null || _a === void 0 ? void 0 : _a.call(this, this.value, app.canvas, node, pos, event);
219
+ }, 20);
220
+ }
221
+ }
222
+ return true;
223
+ },
224
+ });
225
+ widget.doModeChange = (force, skipOtherNodeCheck) => {
226
+ var _a, _b, _c;
227
+ group.recomputeInsideNodes();
228
+ const hasAnyActiveNodes = group._nodes.some((n) => n.mode === LiteGraph.ALWAYS);
229
+ let newValue = force != null ? force : !hasAnyActiveNodes;
230
+ if (skipOtherNodeCheck !== true) {
231
+ if (newValue && ((_b = (_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
232
+ for (const widget of this.widgets) {
233
+ widget.doModeChange(false, true);
234
+ }
235
+ }
236
+ else if (!newValue && ((_c = this.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION]) === "always one") {
237
+ newValue = this.widgets.every((w) => !w.value || w === widget);
238
+ }
239
+ }
240
+ for (const node of group._nodes) {
241
+ node.mode = (newValue ? this.modeOn : this.modeOff);
242
+ }
243
+ group._rgthreeHasAnyActiveNode = newValue;
244
+ widget.value = newValue;
245
+ app.graph.setDirtyCanvas(true, false);
246
+ };
247
+ widget.callback = () => {
248
+ widget.doModeChange();
249
+ };
250
+ this.setSize(this.computeSize());
251
+ }
252
+ if (widget.name != widgetName) {
253
+ widget.name = widgetName;
254
+ this.setDirtyCanvas(true, false);
255
+ }
256
+ if (widget.value != group._rgthreeHasAnyActiveNode) {
257
+ widget.value = group._rgthreeHasAnyActiveNode;
258
+ this.setDirtyCanvas(true, false);
259
+ }
260
+ if (this.widgets[index] !== widget) {
261
+ const oldIndex = this.widgets.findIndex((w) => w === widget);
262
+ this.widgets.splice(index, 0, this.widgets.splice(oldIndex, 1)[0]);
263
+ this.setDirtyCanvas(true, false);
264
+ }
265
+ index++;
266
+ }
267
+ while ((this.widgets || [])[index]) {
268
+ this.removeWidget(index++);
269
+ }
270
+ }
271
+ computeSize(out) {
272
+ let size = super.computeSize(out);
273
+ if (this.tempSize) {
274
+ size[0] = Math.max(this.tempSize[0], size[0]);
275
+ size[1] = Math.max(this.tempSize[1], size[1]);
276
+ this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
277
+ this.debouncerTempWidth = setTimeout(() => {
278
+ this.tempSize = null;
279
+ }, 32);
280
+ }
281
+ setTimeout(() => {
282
+ app.graph.setDirtyCanvas(true, true);
283
+ }, 16);
284
+ return size;
285
+ }
286
+ async handleAction(action) {
287
+ var _a, _b, _c, _d, _e;
288
+ if (action === "Mute all" || action === "Bypass all") {
289
+ const alwaysOne = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === "always one";
290
+ for (const [index, widget] of this.widgets.entries()) {
291
+ widget === null || widget === void 0 ? void 0 : widget.doModeChange(alwaysOne && !index ? true : false, true);
292
+ }
293
+ }
294
+ else if (action === "Enable all") {
295
+ const onlyOne = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_RESTRICTION].includes(" one");
296
+ for (const [index, widget] of this.widgets.entries()) {
297
+ widget === null || widget === void 0 ? void 0 : widget.doModeChange(onlyOne && index > 0 ? false : true, true);
298
+ }
299
+ }
300
+ else if (action === "Toggle all") {
301
+ const onlyOne = (_c = this.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION].includes(" one");
302
+ let foundOne = false;
303
+ for (const [index, widget] of this.widgets.entries()) {
304
+ let newValue = onlyOne && foundOne ? false : !widget.value;
305
+ foundOne = foundOne || newValue;
306
+ widget === null || widget === void 0 ? void 0 : widget.doModeChange(newValue, true);
307
+ }
308
+ if (!foundOne && ((_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_RESTRICTION]) === "always one") {
309
+ (_e = this.widgets[this.widgets.length - 1]) === null || _e === void 0 ? void 0 : _e.doModeChange(true, true);
310
+ }
311
+ }
312
+ }
313
+ getHelp() {
314
+ return `
315
+ <p>The ${this.type.replace("(rgthree)", "")} is an input-less node that automatically collects all groups in your current
316
+ workflow and allows you to quickly ${this.helpActions} all nodes within the group.</p>
317
+ <ul>
318
+ <li>
319
+ <p>
320
+ <strong>Properties.</strong> You can change the following properties (by right-clicking
321
+ on the node, and select "Properties" or "Properties Panel" from the menu):
322
+ </p>
323
+ <ul>
324
+ <li><p>
325
+ <code>${PROPERTY_MATCH_COLORS}</code> - Only add groups that match the provided
326
+ colors. Can be ComfyUI colors (red, pale_blue) or hex codes (#a4d399). Multiple can be
327
+ added, comma delimited.
328
+ </p></li>
329
+ <li><p>
330
+ <code>${PROPERTY_MATCH_TITLE}</code> - Filter the list of toggles by title match
331
+ (string match, or regular expression).
332
+ </p></li>
333
+ <li><p>
334
+ <code>${PROPERTY_SHOW_NAV}</code> - Add / remove a quick navigation arrow to take you
335
+ to the group. <i>(default: true)</i>
336
+ </p></li>
337
+ <li><p>
338
+ <code>${PROPERTY_SORT}</code> - Sort the toggles' order by "alphanumeric", graph
339
+ "position", or "custom alphabet". <i>(default: "position")</i>
340
+ </p></li>
341
+ <li>
342
+ <p>
343
+ <code>${PROPERTY_SORT_CUSTOM_ALPHA}</code> - When the
344
+ <code>${PROPERTY_SORT}</code> property is "custom alphabet" you can define the
345
+ alphabet to use here, which will match the <i>beginning</i> of each group name and
346
+ sort against it. If group titles do not match any custom alphabet entry, then they
347
+ will be put after groups that do, ordered alphanumerically.
348
+ </p>
349
+ <p>
350
+ This can be a list of single characters, like "zyxw..." or comma delimited strings
351
+ for more control, like "sdxl,pro,sd,n,p".
352
+ </p>
353
+ <p>
354
+ Note, when two group title match the same custom alphabet entry, the <i>normal
355
+ alphanumeric alphabet</i> breaks the tie. For instance, a custom alphabet of
356
+ "e,s,d" will order groups names like "SDXL, SEGS, Detailer" eventhough the custom
357
+ alphabet has an "e" before "d" (where one may expect "SE" to be before "SD").
358
+ </p>
359
+ <p>
360
+ To have "SEGS" appear before "SDXL" you can use longer strings. For instance, the
361
+ custom alphabet value of "se,s,f" would work here.
362
+ </p>
363
+ </li>
364
+ <li><p>
365
+ <code>${PROPERTY_RESTRICTION}</code> - Optionally, attempt to restrict the number of
366
+ widgets that can be enabled to a maximum of one, or always one.
367
+ </p>
368
+ <p><em><strong>Note:</strong> If using "max one" or "always one" then this is only
369
+ enforced when clicking a toggle on this node; if nodes within groups are changed
370
+ outside of the initial toggle click, then these restriction will not be enforced, and
371
+ could result in a state where more than one toggle is enabled. This could also happen
372
+ if nodes are overlapped with multiple groups.
373
+ </p></li>
374
+
375
+ </ul>
376
+ </li>
377
+ </ul>`;
378
+ }
379
+ }
380
+ BaseFastGroupsModeChanger.type = NodeTypesString.FAST_GROUPS_MUTER;
381
+ BaseFastGroupsModeChanger.title = NodeTypesString.FAST_GROUPS_MUTER;
382
+ BaseFastGroupsModeChanger.exposedActions = ["Mute all", "Enable all", "Toggle all"];
383
+ BaseFastGroupsModeChanger["@matchColors"] = { type: "string" };
384
+ BaseFastGroupsModeChanger["@matchTitle"] = { type: "string" };
385
+ BaseFastGroupsModeChanger["@showNav"] = { type: "boolean" };
386
+ BaseFastGroupsModeChanger["@sort"] = {
387
+ type: "combo",
388
+ values: ["position", "alphanumeric", "custom alphabet"],
389
+ };
390
+ BaseFastGroupsModeChanger["@customSortAlphabet"] = { type: "string" };
391
+ BaseFastGroupsModeChanger["@toggleRestriction"] = {
392
+ type: "combo",
393
+ values: ["default", "max one", "always one"],
394
+ };
395
+ export class FastGroupsMuter extends BaseFastGroupsModeChanger {
396
+ constructor(title = FastGroupsMuter.title) {
397
+ super(title);
398
+ this.comfyClass = NodeTypesString.FAST_GROUPS_MUTER;
399
+ this.helpActions = "mute and unmute";
400
+ this.modeOn = LiteGraph.ALWAYS;
401
+ this.modeOff = LiteGraph.NEVER;
402
+ this.onConstructed();
403
+ }
404
+ }
405
+ FastGroupsMuter.type = NodeTypesString.FAST_GROUPS_MUTER;
406
+ FastGroupsMuter.title = NodeTypesString.FAST_GROUPS_MUTER;
407
+ FastGroupsMuter.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
408
+ app.registerExtension({
409
+ name: "rgthree.FastGroupsMuter",
410
+ registerCustomNodes() {
411
+ FastGroupsMuter.setUp();
412
+ },
413
+ loadedGraphNode(node) {
414
+ if (node.type == FastGroupsMuter.title) {
415
+ node.tempSize = [...node.size];
416
+ }
417
+ },
418
+ });
rgthree-comfy/web/comfyui/feature_group_fast_toggle.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { rgthree } from "./rgthree.js";
3
+ import { getOutputNodes } from "./utils.js";
4
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
5
+ const BTN_SIZE = 20;
6
+ const BTN_MARGIN = [6, 6];
7
+ const BTN_SPACING = 8;
8
+ const BTN_GRID = BTN_SIZE / 8;
9
+ const TOGGLE_TO_MODE = new Map([
10
+ ["MUTE", LiteGraph.NEVER],
11
+ ["BYPASS", 4],
12
+ ]);
13
+ function getToggles() {
14
+ return [...CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.toggles", [])].reverse();
15
+ }
16
+ function clickedOnToggleButton(e, group) {
17
+ const toggles = getToggles();
18
+ const pos = group.pos;
19
+ const size = group.size;
20
+ for (let i = 0; i < toggles.length; i++) {
21
+ const toggle = toggles[i];
22
+ if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, pos[0] + size[0] - (BTN_SIZE + BTN_MARGIN[0]) * (i + 1), pos[1] + BTN_MARGIN[1], BTN_SIZE, BTN_SIZE)) {
23
+ return toggle;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+ app.registerExtension({
29
+ name: "rgthree.GroupHeaderToggles",
30
+ async setup() {
31
+ setInterval(() => {
32
+ if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") &&
33
+ CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
34
+ app.canvas.setDirty(true, true);
35
+ }
36
+ }, 250);
37
+ rgthree.addEventListener("on-process-mouse-down", ((e) => {
38
+ if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled"))
39
+ return;
40
+ const canvas = app.canvas;
41
+ if (canvas.selected_group) {
42
+ const originalEvent = e.detail.originalEvent;
43
+ const group = canvas.selected_group;
44
+ const clickedOnToggle = clickedOnToggleButton(originalEvent, group) || "";
45
+ const toggleAction = clickedOnToggle === null || clickedOnToggle === void 0 ? void 0 : clickedOnToggle.toLocaleUpperCase();
46
+ if (toggleAction) {
47
+ if (toggleAction === "QUEUE") {
48
+ const outputNodes = getOutputNodes(group._nodes);
49
+ if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
50
+ rgthree.showMessage({
51
+ id: "no-output-in-group",
52
+ type: "warn",
53
+ timeout: 4000,
54
+ message: "No output nodes for group!",
55
+ });
56
+ }
57
+ else {
58
+ rgthree.queueOutputNodes(outputNodes.map((n) => n.id));
59
+ }
60
+ }
61
+ else {
62
+ const toggleMode = TOGGLE_TO_MODE.get(toggleAction);
63
+ if (toggleMode) {
64
+ group.recomputeInsideNodes();
65
+ const hasAnyActiveNodes = group._nodes.some((n) => n.mode === LiteGraph.ALWAYS);
66
+ const isAllMuted = !hasAnyActiveNodes && group._nodes.every((n) => n.mode === LiteGraph.NEVER);
67
+ const isAllBypassed = !hasAnyActiveNodes && !isAllMuted && group._nodes.every((n) => n.mode === 4);
68
+ let newMode = LiteGraph.ALWAYS;
69
+ if (toggleMode === LiteGraph.NEVER) {
70
+ newMode = isAllMuted ? LiteGraph.ALWAYS : LiteGraph.NEVER;
71
+ }
72
+ else {
73
+ newMode = isAllBypassed ? LiteGraph.ALWAYS : 4;
74
+ }
75
+ for (const node of group._nodes) {
76
+ node.mode = newMode;
77
+ }
78
+ }
79
+ }
80
+ canvas.selected_group = null;
81
+ canvas.dragging_canvas = false;
82
+ }
83
+ }
84
+ }));
85
+ const drawGroups = LGraphCanvas.prototype.drawGroups;
86
+ LGraphCanvas.prototype.drawGroups = function (canvasEl, ctx) {
87
+ drawGroups.apply(this, [...arguments]);
88
+ if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") ||
89
+ !rgthree.lastAdjustedMouseEvent) {
90
+ return;
91
+ }
92
+ const graph = app.graph;
93
+ let groups;
94
+ if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
95
+ const hoverGroup = graph.getGroupOnPos(rgthree.lastAdjustedMouseEvent.canvasX, rgthree.lastAdjustedMouseEvent.canvasY);
96
+ groups = hoverGroup ? [hoverGroup] : [];
97
+ }
98
+ else {
99
+ groups = graph._groups || [];
100
+ }
101
+ if (!groups.length) {
102
+ return;
103
+ }
104
+ const toggles = getToggles();
105
+ ctx.save();
106
+ for (const group of groups || []) {
107
+ let anyActive = false;
108
+ let allMuted = !!group._nodes.length;
109
+ let allBypassed = allMuted;
110
+ for (const node of group._nodes) {
111
+ anyActive = anyActive || node.mode === LiteGraph.ALWAYS;
112
+ allMuted = allMuted && node.mode === LiteGraph.NEVER;
113
+ allBypassed = allBypassed && node.mode === 4;
114
+ if (anyActive || (!allMuted && !allBypassed)) {
115
+ break;
116
+ }
117
+ }
118
+ for (let i = 0; i < toggles.length; i++) {
119
+ const toggle = toggles[i];
120
+ const pos = group._pos;
121
+ const size = group._size;
122
+ ctx.fillStyle = ctx.strokeStyle = group.color || "#335";
123
+ const x = pos[0] + size[0] - BTN_MARGIN[0] - BTN_SIZE - (BTN_SPACING + BTN_SIZE) * i;
124
+ const y = pos[1] + BTN_MARGIN[1];
125
+ const midX = x + BTN_SIZE / 2;
126
+ const midY = y + BTN_SIZE / 2;
127
+ if (toggle === "queue") {
128
+ const outputNodes = getOutputNodes(group._nodes);
129
+ const oldGlobalAlpha = ctx.globalAlpha;
130
+ if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
131
+ ctx.globalAlpha = 0.5;
132
+ }
133
+ ctx.lineJoin = "round";
134
+ ctx.lineCap = "round";
135
+ const arrowSizeX = BTN_SIZE * 0.6;
136
+ const arrowSizeY = BTN_SIZE * 0.7;
137
+ const arrow = new Path2D(`M ${x + arrowSizeX / 2} ${midY} l 0 -${arrowSizeY / 2} l ${arrowSizeX} ${arrowSizeY / 2} l -${arrowSizeX} ${arrowSizeY / 2} z`);
138
+ ctx.stroke(arrow);
139
+ if (outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length) {
140
+ ctx.fill(arrow);
141
+ }
142
+ ctx.globalAlpha = oldGlobalAlpha;
143
+ }
144
+ else {
145
+ const on = toggle === "bypass" ? allBypassed : allMuted;
146
+ ctx.beginPath();
147
+ ctx.lineJoin = "round";
148
+ ctx.rect(x, y, BTN_SIZE, BTN_SIZE);
149
+ ctx.lineWidth = 2;
150
+ if (toggle === "mute") {
151
+ ctx.lineJoin = "round";
152
+ ctx.lineCap = "round";
153
+ if (on) {
154
+ ctx.stroke(new Path2D(`
155
+ ${eyeFrame(midX, midY)}
156
+ ${eyeLashes(midX, midY)}
157
+ `));
158
+ }
159
+ else {
160
+ const radius = BTN_GRID * 1.5;
161
+ ctx.fill(new Path2D(`
162
+ ${eyeFrame(midX, midY)}
163
+ ${eyeFrame(midX, midY, -1)}
164
+ ${circlePath(midX, midY, radius)}
165
+ ${circlePath(midX + BTN_GRID / 2, midY - BTN_GRID / 2, BTN_GRID * 0.375)}
166
+ `), "evenodd");
167
+ ctx.stroke(new Path2D(`${eyeFrame(midX, midY)} ${eyeFrame(midX, midY, -1)}`));
168
+ ctx.globalAlpha = this.editor_alpha * 0.5;
169
+ ctx.stroke(new Path2D(`${eyeLashes(midX, midY)} ${eyeLashes(midX, midY, -1)}`));
170
+ ctx.globalAlpha = this.editor_alpha;
171
+ }
172
+ }
173
+ else {
174
+ const lineChanges = on
175
+ ? `a ${BTN_GRID * 3}, ${BTN_GRID * 3} 0 1, 1 ${BTN_GRID * 3 * 2},0
176
+ l ${BTN_GRID * 2.0} 0`
177
+ : `l ${BTN_GRID * 8} 0`;
178
+ ctx.stroke(new Path2D(`
179
+ M ${x} ${midY}
180
+ ${lineChanges}
181
+ M ${x + BTN_SIZE} ${midY} l -2 2
182
+ M ${x + BTN_SIZE} ${midY} l -2 -2
183
+ `));
184
+ ctx.fill(new Path2D(`${circlePath(x + BTN_GRID * 3, midY, BTN_GRID * 1.8)}`));
185
+ }
186
+ }
187
+ }
188
+ }
189
+ ctx.restore();
190
+ };
191
+ },
192
+ });
193
+ function eyeFrame(midX, midY, yFlip = 1) {
194
+ return `
195
+ M ${midX - BTN_SIZE / 2} ${midY}
196
+ c ${BTN_GRID * 1.5} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * (8 - 1.5)} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * 8} 0
197
+ `;
198
+ }
199
+ function eyeLashes(midX, midY, yFlip = 1) {
200
+ return `
201
+ M ${midX - BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l -1.15 ${1.25 * yFlip}
202
+ M ${midX - BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l -0.90 ${1.5 * yFlip}
203
+ M ${midX - BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l -0.50 ${1.75 * yFlip}
204
+ M ${midX + BTN_GRID * 0.0} ${midY + yFlip * BTN_GRID * 2.0} l 0.00 ${2.0 * yFlip}
205
+ M ${midX + BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l 0.50 ${1.75 * yFlip}
206
+ M ${midX + BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l 0.90 ${1.5 * yFlip}
207
+ M ${midX + BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l 1.15 ${1.25 * yFlip}
208
+ `;
209
+ }
210
+ function circlePath(cx, cy, radius) {
211
+ return `
212
+ M ${cx} ${cy}
213
+ m ${radius}, 0
214
+ a ${radius},${radius} 0 1, 1 -${radius * 2},0
215
+ a ${radius},${radius} 0 1, 1 ${radius * 2},0
216
+ `;
217
+ }
rgthree-comfy/web/comfyui/feature_import_individual_nodes.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { tryToGetWorkflowDataFromEvent } from "../../rgthree/common/utils_workflow.js";
2
+ import { app } from "../../scripts/app.js";
3
+ import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
4
+ app.registerExtension({
5
+ name: "rgthree.ImportIndividualNodes",
6
+ async beforeRegisterNodeDef(nodeType, nodeData) {
7
+ const onDragOver = nodeType.prototype.onDragOver;
8
+ nodeType.prototype.onDragOver = function (e) {
9
+ var _a;
10
+ let handled = (_a = onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver.apply) === null || _a === void 0 ? void 0 : _a.call(onDragOver, this, [...arguments]);
11
+ if (handled != null) {
12
+ return handled;
13
+ }
14
+ return importIndividualNodesInnerOnDragOver(this, e);
15
+ };
16
+ const onDragDrop = nodeType.prototype.onDragDrop;
17
+ nodeType.prototype.onDragDrop = async function (e) {
18
+ var _a;
19
+ const alreadyHandled = await ((_a = onDragDrop === null || onDragDrop === void 0 ? void 0 : onDragDrop.apply) === null || _a === void 0 ? void 0 : _a.call(onDragDrop, this, [...arguments]));
20
+ if (alreadyHandled) {
21
+ return alreadyHandled;
22
+ }
23
+ return importIndividualNodesInnerOnDragDrop(this, e);
24
+ };
25
+ },
26
+ });
27
+ export function importIndividualNodesInnerOnDragOver(node, e) {
28
+ var _a;
29
+ return ((((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) && !!CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) ||
30
+ false);
31
+ }
32
+ export async function importIndividualNodesInnerOnDragDrop(node, e) {
33
+ var _a, _b;
34
+ if (!((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) || !CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) {
35
+ return false;
36
+ }
37
+ let handled = false;
38
+ const { workflow, prompt } = await tryToGetWorkflowDataFromEvent(e);
39
+ if (!handled && workflow) {
40
+ const exact = (workflow.nodes || []).find((n) => n.id === node.id && n.type === node.type);
41
+ if (((_b = exact === null || exact === void 0 ? void 0 : exact.widgets_values) === null || _b === void 0 ? void 0 : _b.length) &&
42
+ confirm("Found a node match from embedded workflow (same id & type) in this workflow. Would you like to set the widget values?")) {
43
+ node.configure({
44
+ title: node.title,
45
+ widgets_values: [...((exact === null || exact === void 0 ? void 0 : exact.widgets_values) || [])]
46
+ });
47
+ handled = true;
48
+ }
49
+ }
50
+ if (!handled && workflow) {
51
+ handled = !confirm("No exact match found in workflow. Would you like to replace the whole workflow?");
52
+ }
53
+ return handled;
54
+ }
rgthree-comfy/web/comfyui/image_comparer.js ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { api } from "../../scripts/api.js";
3
+ import { RgthreeBaseServerNode } from "./base_node.js";
4
+ import { NodeTypesString } from "./constants.js";
5
+ import { addConnectionLayoutSupport } from "./utils.js";
6
+ import { RgthreeBaseWidget, } from "./utils_widgets.js";
7
+ import { measureText } from "./utils_canvas.js";
8
+ function imageDataToUrl(data) {
9
+ return api.apiURL(`/view?filename=${encodeURIComponent(data.filename)}&type=${data.type}&subfolder=${data.subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
10
+ }
11
+ export class RgthreeImageComparer extends RgthreeBaseServerNode {
12
+ constructor(title = RgthreeImageComparer.title) {
13
+ super(title);
14
+ this.imageIndex = 0;
15
+ this.imgs = [];
16
+ this.serialize_widgets = true;
17
+ this.isPointerDown = false;
18
+ this.isPointerOver = false;
19
+ this.pointerOverPos = [0, 0];
20
+ this.canvasWidget = null;
21
+ this.properties["comparer_mode"] = "Slide";
22
+ }
23
+ onExecuted(output) {
24
+ var _a;
25
+ (_a = super.onExecuted) === null || _a === void 0 ? void 0 : _a.call(this, output);
26
+ if ("images" in output) {
27
+ this.canvasWidget.value = {
28
+ images: (output.images || []).map((d, i) => {
29
+ return {
30
+ name: i === 0 ? "A" : "B",
31
+ selected: true,
32
+ url: imageDataToUrl(d),
33
+ };
34
+ }),
35
+ };
36
+ }
37
+ else {
38
+ output.a_images = output.a_images || [];
39
+ output.b_images = output.b_images || [];
40
+ const imagesToChoose = [];
41
+ const multiple = output.a_images.length + output.b_images.length > 2;
42
+ for (const [i, d] of output.a_images.entries()) {
43
+ imagesToChoose.push({
44
+ name: output.a_images.length > 1 || multiple ? `A${i + 1}` : "A",
45
+ selected: i === 0,
46
+ url: imageDataToUrl(d),
47
+ });
48
+ }
49
+ for (const [i, d] of output.b_images.entries()) {
50
+ imagesToChoose.push({
51
+ name: output.b_images.length > 1 || multiple ? `B${i + 1}` : "B",
52
+ selected: i === 0,
53
+ url: imageDataToUrl(d),
54
+ });
55
+ }
56
+ this.canvasWidget.value = { images: imagesToChoose };
57
+ }
58
+ }
59
+ onSerialize(o) {
60
+ var _a;
61
+ super.onSerialize && super.onSerialize(o);
62
+ for (let [index, widget_value] of (o.widgets_values || []).entries()) {
63
+ if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "rgthree_comparer") {
64
+ o.widgets_values[index] = this.widgets[index].value.images.map((d) => {
65
+ d = { ...d };
66
+ delete d.img;
67
+ return d;
68
+ });
69
+ }
70
+ }
71
+ }
72
+ onNodeCreated() {
73
+ this.canvasWidget = this.addCustomWidget(new RgthreeImageComparerWidget("rgthree_comparer", this));
74
+ this.setSize(this.computeSize());
75
+ this.setDirtyCanvas(true, true);
76
+ }
77
+ setIsPointerDown(down = this.isPointerDown) {
78
+ const newIsDown = down && !!app.canvas.pointer_is_down;
79
+ if (this.isPointerDown !== newIsDown) {
80
+ this.isPointerDown = newIsDown;
81
+ this.setDirtyCanvas(true, false);
82
+ }
83
+ this.imageIndex = this.isPointerDown ? 1 : 0;
84
+ if (this.isPointerDown) {
85
+ requestAnimationFrame(() => {
86
+ this.setIsPointerDown();
87
+ });
88
+ }
89
+ }
90
+ onMouseDown(event, pos, graphCanvas) {
91
+ var _a;
92
+ (_a = super.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas);
93
+ this.setIsPointerDown(true);
94
+ }
95
+ onMouseEnter(event, pos, graphCanvas) {
96
+ var _a;
97
+ (_a = super.onMouseEnter) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas);
98
+ this.setIsPointerDown(!!app.canvas.pointer_is_down);
99
+ this.isPointerOver = true;
100
+ }
101
+ onMouseLeave(event, pos, graphCanvas) {
102
+ var _a;
103
+ (_a = super.onMouseLeave) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas);
104
+ this.setIsPointerDown(false);
105
+ this.isPointerOver = false;
106
+ }
107
+ onMouseMove(event, pos, graphCanvas) {
108
+ var _a;
109
+ (_a = super.onMouseMove) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, graphCanvas);
110
+ this.pointerOverPos = [...pos];
111
+ this.imageIndex = this.pointerOverPos[0] > this.size[0] / 2 ? 1 : 0;
112
+ }
113
+ getHelp() {
114
+ return `
115
+ <p>
116
+ The ${this.type.replace("(rgthree)", "")} node compares two images on top of each other.
117
+ </p>
118
+ <ul>
119
+ <li>
120
+ <p>
121
+ <strong>Notes</strong>
122
+ </p>
123
+ <ul>
124
+ <li><p>
125
+ The right-click menu may show image options (Open Image, Save Image, etc.) which will
126
+ correspond to the first image (image_a) if clicked on the left-half of the node, or
127
+ the second image if on the right half of the node.
128
+ </p></li>
129
+ </ul>
130
+ </li>
131
+ <li>
132
+ <p>
133
+ <strong>Inputs</strong>
134
+ </p>
135
+ <ul>
136
+ <li><p>
137
+ <code>image_a</code> <i>Optional.</i> The first image to use to compare.
138
+ image_a.
139
+ </p></li>
140
+ <li><p>
141
+ <code>image_b</code> <i>Optional.</i> The second image to use to compare.
142
+ </p></li>
143
+ <li><p>
144
+ <b>Note</b> <code>image_a</code> and <code>image_b</code> work best when a single
145
+ image is provided. However, if each/either are a batch, you can choose which item
146
+ from each batch are chosen to be compared. If either <code>image_a</code> or
147
+ <code>image_b</code> are not provided, the node will choose the first two from the
148
+ provided input if it's a batch, otherwise only show the single image (just as
149
+ Preview Image would).
150
+ </p></li>
151
+ </ul>
152
+ </li>
153
+ <li>
154
+ <p>
155
+ <strong>Properties.</strong> You can change the following properties (by right-clicking
156
+ on the node, and select "Properties" or "Properties Panel" from the menu):
157
+ </p>
158
+ <ul>
159
+ <li><p>
160
+ <code>comparer_mode</code> - Choose between "Slide" and "Click". Defaults to "Slide".
161
+ </p></li>
162
+ </ul>
163
+ </li>
164
+ </ul>`;
165
+ }
166
+ static setUp(comfyClass, nodeData) {
167
+ RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeImageComparer);
168
+ }
169
+ static onRegisteredForOverride(comfyClass) {
170
+ addConnectionLayoutSupport(RgthreeImageComparer, app, [
171
+ ["Left", "Right"],
172
+ ["Right", "Left"],
173
+ ]);
174
+ setTimeout(() => {
175
+ RgthreeImageComparer.category = comfyClass.category;
176
+ });
177
+ }
178
+ }
179
+ RgthreeImageComparer.title = NodeTypesString.IMAGE_COMPARER;
180
+ RgthreeImageComparer.type = NodeTypesString.IMAGE_COMPARER;
181
+ RgthreeImageComparer.comfyClass = NodeTypesString.IMAGE_COMPARER;
182
+ RgthreeImageComparer["@comparer_mode"] = {
183
+ type: "combo",
184
+ values: ["Slide", "Click"],
185
+ };
186
+ class RgthreeImageComparerWidget extends RgthreeBaseWidget {
187
+ constructor(name, node) {
188
+ super(name);
189
+ this.hitAreas = {};
190
+ this.selected = [];
191
+ this._value = { images: [] };
192
+ this.node = node;
193
+ }
194
+ set value(v) {
195
+ let cleanedVal;
196
+ if (Array.isArray(v)) {
197
+ cleanedVal = v.map((d, i) => {
198
+ if (!d || typeof d === "string") {
199
+ d = { url: d, name: i == 0 ? "A" : "B", selected: true };
200
+ }
201
+ return d;
202
+ });
203
+ }
204
+ else {
205
+ cleanedVal = v.images || [];
206
+ }
207
+ if (cleanedVal.length > 2) {
208
+ const hasAAndB = cleanedVal.some((i) => i.name.startsWith("A")) &&
209
+ cleanedVal.some((i) => i.name.startsWith("B"));
210
+ if (!hasAAndB) {
211
+ cleanedVal = [cleanedVal[0], cleanedVal[1]];
212
+ }
213
+ }
214
+ let selected = cleanedVal.filter((d) => d.selected);
215
+ if (!selected.length && cleanedVal.length) {
216
+ cleanedVal[0].selected = true;
217
+ }
218
+ selected = cleanedVal.filter((d) => d.selected);
219
+ if (selected.length === 1 && cleanedVal.length > 1) {
220
+ cleanedVal.find((d) => !d.selected).selected = true;
221
+ }
222
+ this._value.images = cleanedVal;
223
+ selected = cleanedVal.filter((d) => d.selected);
224
+ this.setSelected(selected);
225
+ }
226
+ get value() {
227
+ return this._value;
228
+ }
229
+ setSelected(selected) {
230
+ this._value.images.forEach((d) => (d.selected = false));
231
+ this.node.imgs.length = 0;
232
+ for (const sel of selected) {
233
+ if (!sel.img) {
234
+ sel.img = new Image();
235
+ sel.img.src = sel.url;
236
+ this.node.imgs.push(sel.img);
237
+ }
238
+ sel.selected = true;
239
+ }
240
+ this.selected = selected;
241
+ }
242
+ draw(ctx, node, width, y) {
243
+ var _a;
244
+ this.hitAreas = {};
245
+ if (this.value.images.length > 2) {
246
+ ctx.textAlign = "left";
247
+ ctx.textBaseline = "top";
248
+ ctx.font = `14px Arial`;
249
+ const drawData = [];
250
+ const spacing = 5;
251
+ let x = 0;
252
+ for (const img of this.value.images) {
253
+ const width = measureText(ctx, img.name);
254
+ drawData.push({
255
+ img,
256
+ text: img.name,
257
+ x,
258
+ width: measureText(ctx, img.name),
259
+ });
260
+ x += width + spacing;
261
+ }
262
+ x = (node.size[0] - (x - spacing)) / 2;
263
+ for (const d of drawData) {
264
+ ctx.fillStyle = d.img.selected ? "rgba(180, 180, 180, 1)" : "rgba(180, 180, 180, 0.5)";
265
+ ctx.fillText(d.text, x, y);
266
+ this.hitAreas[d.text] = {
267
+ bounds: [x, y, d.width, 14],
268
+ data: d.img,
269
+ onDown: this.onSelectionDown,
270
+ };
271
+ x += d.width + spacing;
272
+ }
273
+ y += 20;
274
+ }
275
+ if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a["comparer_mode"]) === "Click") {
276
+ this.drawImage(ctx, this.selected[this.node.isPointerDown ? 1 : 0], y);
277
+ }
278
+ else {
279
+ this.drawImage(ctx, this.selected[0], y);
280
+ if (node.isPointerOver) {
281
+ this.drawImage(ctx, this.selected[1], y, this.node.pointerOverPos[0]);
282
+ }
283
+ }
284
+ }
285
+ onSelectionDown(event, pos, node, bounds) {
286
+ const selected = [...this.selected];
287
+ if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("A")) {
288
+ selected[0] = bounds.data;
289
+ }
290
+ else if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("B")) {
291
+ selected[1] = bounds.data;
292
+ }
293
+ this.setSelected(selected);
294
+ }
295
+ drawImage(ctx, image, y, cropX) {
296
+ var _a, _b;
297
+ if (!((_a = image === null || image === void 0 ? void 0 : image.img) === null || _a === void 0 ? void 0 : _a.naturalWidth) || !((_b = image === null || image === void 0 ? void 0 : image.img) === null || _b === void 0 ? void 0 : _b.naturalHeight)) {
298
+ return;
299
+ }
300
+ let [nodeWidth, nodeHeight] = this.node.size;
301
+ const imageAspect = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / (image === null || image === void 0 ? void 0 : image.img.naturalHeight);
302
+ let height = nodeHeight - y;
303
+ const widgetAspect = nodeWidth / height;
304
+ let targetWidth, targetHeight;
305
+ let offsetX = 0;
306
+ if (imageAspect > widgetAspect) {
307
+ targetWidth = nodeWidth;
308
+ targetHeight = nodeWidth / imageAspect;
309
+ }
310
+ else {
311
+ targetHeight = height;
312
+ targetWidth = height * imageAspect;
313
+ offsetX = (nodeWidth - targetWidth) / 2;
314
+ }
315
+ const widthMultiplier = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / targetWidth;
316
+ const sourceX = 0;
317
+ const sourceY = 0;
318
+ const sourceWidth = cropX != null ? (cropX - offsetX) * widthMultiplier : image === null || image === void 0 ? void 0 : image.img.naturalWidth;
319
+ const sourceHeight = image === null || image === void 0 ? void 0 : image.img.naturalHeight;
320
+ const destX = (nodeWidth - targetWidth) / 2;
321
+ const destY = y + (height - targetHeight) / 2;
322
+ const destWidth = cropX != null ? cropX - offsetX : targetWidth;
323
+ const destHeight = targetHeight;
324
+ ctx.save();
325
+ ctx.beginPath();
326
+ let globalCompositeOperation = ctx.globalCompositeOperation;
327
+ if (cropX) {
328
+ ctx.rect(destX, destY, destWidth, destHeight);
329
+ ctx.clip();
330
+ }
331
+ ctx.drawImage(image === null || image === void 0 ? void 0 : image.img, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
332
+ if (cropX != null && cropX >= (nodeWidth - targetWidth) / 2 && cropX <= targetWidth + offsetX) {
333
+ ctx.beginPath();
334
+ ctx.moveTo(cropX, destY);
335
+ ctx.lineTo(cropX, destY + destHeight);
336
+ ctx.globalCompositeOperation = "difference";
337
+ ctx.strokeStyle = "rgba(255,255,255, 1)";
338
+ ctx.stroke();
339
+ }
340
+ ctx.globalCompositeOperation = globalCompositeOperation;
341
+ ctx.restore();
342
+ }
343
+ computeSize(width) {
344
+ return [width, 20];
345
+ }
346
+ serializeValue(serializedNode, widgetIndex) {
347
+ const v = [];
348
+ for (const data of this._value.images) {
349
+ const d = { ...data };
350
+ delete d.img;
351
+ v.push(d);
352
+ }
353
+ return { images: v };
354
+ }
355
+ }
356
+ app.registerExtension({
357
+ name: "rgthree.ImageComparer",
358
+ async beforeRegisterNodeDef(nodeType, nodeData) {
359
+ if (nodeData.name === RgthreeImageComparer.type) {
360
+ RgthreeImageComparer.setUp(nodeType, nodeData);
361
+ }
362
+ },
363
+ });
rgthree-comfy/web/comfyui/image_inset_crop.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { RgthreeBaseServerNode } from "./base_node.js";
3
+ import { NodeTypesString } from "./constants.js";
4
+ class ImageInsetCrop extends RgthreeBaseServerNode {
5
+ constructor(title = ImageInsetCrop.title) {
6
+ super(title);
7
+ }
8
+ onAdded(graph) {
9
+ const measurementWidget = this.widgets[0];
10
+ let callback = measurementWidget.callback;
11
+ measurementWidget.callback = (...args) => {
12
+ this.setWidgetStep();
13
+ callback && callback.apply(measurementWidget, [...args]);
14
+ };
15
+ this.setWidgetStep();
16
+ }
17
+ configure(info) {
18
+ super.configure(info);
19
+ this.setWidgetStep();
20
+ }
21
+ setWidgetStep() {
22
+ const measurementWidget = this.widgets[0];
23
+ for (let i = 1; i <= 4; i++) {
24
+ if (measurementWidget.value === "Pixels") {
25
+ this.widgets[i].options.step = 80;
26
+ this.widgets[i].options.max = ImageInsetCrop.maxResolution;
27
+ }
28
+ else {
29
+ this.widgets[i].options.step = 10;
30
+ this.widgets[i].options.max = 99;
31
+ }
32
+ }
33
+ }
34
+ async handleAction(action) {
35
+ if (action === "Reset Crop") {
36
+ for (const widget of this.widgets) {
37
+ if (["left", "right", "top", "bottom"].includes(widget.name)) {
38
+ widget.value = 0;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ static setUp(comfyClass, nodeData) {
44
+ RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ImageInsetCrop);
45
+ }
46
+ }
47
+ ImageInsetCrop.title = NodeTypesString.IMAGE_INSET_CROP;
48
+ ImageInsetCrop.type = NodeTypesString.IMAGE_INSET_CROP;
49
+ ImageInsetCrop.comfyClass = NodeTypesString.IMAGE_INSET_CROP;
50
+ ImageInsetCrop.exposedActions = ["Reset Crop"];
51
+ ImageInsetCrop.maxResolution = 8192;
52
+ app.registerExtension({
53
+ name: "rgthree.ImageInsetCrop",
54
+ async beforeRegisterNodeDef(nodeType, nodeData, _app) {
55
+ if (nodeData.name === NodeTypesString.IMAGE_INSET_CROP) {
56
+ ImageInsetCrop.setUp(nodeType, nodeData);
57
+ }
58
+ },
59
+ });