diff --git a/litegraph b/litegraph index 9b8d28d..7c38fa4 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit 9b8d28d3e2b3bc69a09b55b2d41a9bd53f57e37d +Subproject commit 7c38fa4aedb33c960806efad6d700ec8ca67649f diff --git a/package.json b/package.json index 7dce293..670e59f 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@codemirror/search": "^6.2.2", "@codemirror/state": "^6.1.2", "@codemirror/view": "^6.4.1", + "@dogagenc/svelte-markdown": "^0.2.4", "@gradio/accordion": "workspace:*", "@gradio/atoms": "workspace:*", "@gradio/button": "workspace:*", @@ -85,6 +86,7 @@ "framework7": "^8.0.3", "framework7-svelte": "^8.0.3", "img-comparison-slider": "^8.0.0", + "marked": "^5.0.3", "pollen-css": "^4.6.2", "radix-icons-svelte": "^1.2.1", "style-mod": "^4.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 460e56e..9f8345e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ importers: '@codemirror/view': specifier: ^6.4.1 version: 6.11.0 + '@dogagenc/svelte-markdown': + specifier: ^0.2.4 + version: 0.2.4(svelte@3.58.0) '@gradio/accordion': specifier: workspace:* version: link:gradio/js/accordion @@ -130,6 +133,9 @@ importers: img-comparison-slider: specifier: ^8.0.0 version: 8.0.0 + marked: + specifier: ^5.0.3 + version: 5.0.3 pollen-css: specifier: ^4.6.2 version: 4.6.2 @@ -1357,7 +1363,6 @@ packages: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.6.0 '@codemirror/state': 6.2.0 @@ -1501,6 +1506,16 @@ packages: w3c-keyname: 2.2.6 dev: false + /@dogagenc/svelte-markdown@0.2.4(svelte@3.58.0): + resolution: {integrity: sha512-UmmHHZ7rilAbBYiNsxuL5d8Ac79EhFXrhjsUNr30BPzn+T7ohJR8kHMFjDYDQc0tOQOfKbICvkPAQ6cprqS3Eg==} + peerDependencies: + svelte: ^3.0.0 + dependencies: + '@types/marked': 4.3.1 + marked: 4.3.0 + svelte: 3.58.0 + dev: false + /@esbuild/android-arm64@0.17.18: resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} engines: {node: '>=12'} @@ -2423,6 +2438,10 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/marked@4.3.1: + resolution: {integrity: sha512-vSSbKZFbNktrQ15v7o1EaH78EbWV+sPQbPjHG+Cp8CaNcPFUEfjZ0Iml/V0bFDwsTlYe8o6XC5Hfdp91cqPV2g==} + dev: false + /@types/node@10.17.60: resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} dev: false @@ -3098,8 +3117,6 @@ packages: '@codemirror/search': 6.4.0 '@codemirror/state': 6.2.0 '@codemirror/view': 6.11.0 - transitivePeerDependencies: - - '@lezer/common' dev: false /codemirror@6.0.1(@lezer/common@1.0.2): @@ -5481,6 +5498,18 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + dev: false + + /marked@5.0.3: + resolution: {integrity: sha512-KUONa43Uk74uUNWMxh6lfaNYmSAsRMiDAaX8QBCCRVXzEufR0zX6T33vrGbvTnQLL02ungDM3KSzZtO+chJaHg==} + engines: {node: '>= 18'} + hasBin: true + dev: false + /md5-hex@3.0.1: resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} engines: {node: '>=8'} diff --git a/src/lib/components/ImageUpload.svelte b/src/lib/components/ImageUpload.svelte index 224ad73..85358df 100644 --- a/src/lib/components/ImageUpload.svelte +++ b/src/lib/components/ImageUpload.svelte @@ -18,6 +18,7 @@ export let elem_classes: string[] = [] export let style: string = "" export let label: string = "" + export let mask: ComfyImageLocation | null; // let propsChanged: Writable | null = null; let dragging = false; let pending_upload = false; @@ -172,6 +173,15 @@ bind:naturalWidth={imgWidth} bind:naturalHeight={imgHeight} /> + {#key mask} + {#if mask} + + {firstImage.filename} + {/if} + {/key} {:else} (widgetNodeType); comfyWidgetNode.flags.collapsed = true; const size: Vector2 = [0, 0]; @@ -85,12 +85,15 @@ function createSerializedWidgetNode(vanillaWorkflow: ComfyVanillaWorkflow, node: const serWidgetNode = comfyWidgetNode.serialize() as SerializedComfyWidgetNode; serWidgetNode.comfyValue = value; serWidgetNode.shownOutputProperties = {}; - getConnectionPos(node, isInput, slotIndex, serWidgetNode.pos); - if (isInput) - serWidgetNode.pos[0] -= size[0] - 20; - else - serWidgetNode.pos[0] += 20; - serWidgetNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2; + + if (node != null) { + getConnectionPos(node, isInput, slotIndex, serWidgetNode.pos); + if (isInput) + serWidgetNode.pos[0] -= size[0] - 20; + else + serWidgetNode.pos[0] += 20; + serWidgetNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2; + } if (widgetNodeType === "ui/text" && typeof value === "string" && value.indexOf("\n") != -1) { const lineCount = countNewLines(value); @@ -260,11 +263,12 @@ function convertPrimitiveNode(vanillaWorkflow: ComfyVanillaWorkflow, node: Seria const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode( vanillaWorkflow, + widgetNodeType, + value, node, 0, // first output on the PrimitiveNode - false, // this is an output slot index - widgetNodeType, - value); + false // this is an output slot index + ); // Set the UI node's min/max/step from the node def configureWidgetNodeProperties(serWidgetNode, widgetOpts) @@ -381,6 +385,20 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork removeSerializedNode(vanillaWorkflow, node); continue } + else if (node.type === "Note") { + const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode( + vanillaWorkflow, + "ui/markdown", + node.widgets_values[0] + ); + serWidgetNode.pos = [node.pos[0], node.pos[1]] + + const group = layoutState.addContainer(left, { title: "" }) + layoutState.addWidget(group, comfyWidgetNode) + + removeSerializedNode(vanillaWorkflow, node); + continue + } const def = ComfyApp.knownBackendNodes[node.type]; if (def == null) { @@ -449,11 +467,12 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode( vanillaWorkflow, + widgetNodeType, + value, node, connInputIndex, - true, - widgetNodeType, - value); + true + ); configureWidgetNodeProperties(serWidgetNode, inputOpts) @@ -492,11 +511,12 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork // Let's create a gallery for this output node and hook it up const [comfyGalleryNode, serGalleryNode] = createSerializedWidgetNode( vanillaWorkflow, + "ui/gallery", + [], node, connOutputIndex, false, - "ui/gallery", - []); + ); if (group == null) group = layoutState.addContainer(isOutputNode ? right : left, { title: node.title || node.type }) diff --git a/src/lib/nodes/widgets/ComfyMarkdownNode.ts b/src/lib/nodes/widgets/ComfyMarkdownNode.ts new file mode 100644 index 0000000..8d41bf5 --- /dev/null +++ b/src/lib/nodes/widgets/ComfyMarkdownNode.ts @@ -0,0 +1,57 @@ +import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout } from "@litegraph-ts/core"; + +import MarkdownWidget from "$lib/widgets/MarkdownWidget.svelte"; +import ComfyWidgetNode, { type ComfyWidgetProperties } from "./ComfyWidgetNode"; + +export interface ComfyMarkdownProperties extends ComfyWidgetProperties { +} + +export default class ComfyMarkdownNode extends ComfyWidgetNode { + override properties: ComfyMarkdownProperties = { + tags: [], + defaultValue: false, + } + + static slotLayout: SlotLayout = { + inputs: [ + { name: "store", type: BuiltInSlotType.ACTION } + ], + outputs: [ + { name: "value", type: "string" }, + { name: "changed", type: BuiltInSlotType.EVENT }, + ] + } + + override svelteComponentType = MarkdownWidget; + override defaultValue = ""; + + constructor(name?: string) { + super(name, "") + } + + override createDisplayWidget(): ITextWidget { + const widget = this.addWidget( + "text", + "Value", + "", + (v: string) => { + if (v == null || v === this.getValue()) { + return; + } + this.setValue(v); + }, + { + multiline: true, + inputStyle: { fontFamily: "monospace" } + } + ) + return widget; + } +} + +LiteGraph.registerNodeType({ + class: ComfyMarkdownNode, + title: "UI.Markdown", + desc: "Displays Markdown in the UI", + type: "ui/markdown" +}) diff --git a/src/lib/nodes/widgets/ComfyWidgetNode.ts b/src/lib/nodes/widgets/ComfyWidgetNode.ts index a8787cb..a0c3c8a 100644 --- a/src/lib/nodes/widgets/ComfyWidgetNode.ts +++ b/src/lib/nodes/widgets/ComfyWidgetNode.ts @@ -106,13 +106,18 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { this.value = writable(value) this.color ||= color.color this.bgColor ||= color.bgColor - this.displayWidget = this.addWidget( + this.displayWidget = this.createDisplayWidget(); + this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this)) + } + + protected createDisplayWidget(): ITextWidget { + const widget = this.addWidget( "text", "Value", "" - ); - this.displayWidget.disabled = true; // prevent editing - this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this)) + ) + widget.disabled = true; // prevent editing + return widget; } addPropertyAsOutput(propertyName: string, type: string) { diff --git a/src/lib/nodes/widgets/index.ts b/src/lib/nodes/widgets/index.ts index 130149c..d1b6c09 100644 --- a/src/lib/nodes/widgets/index.ts +++ b/src/lib/nodes/widgets/index.ts @@ -9,3 +9,4 @@ export { default as ComfyRadioNode } from "./ComfyRadioNode" export { default as ComfyNumberNode } from "./ComfyNumberNode" export { default as ComfyTextNode } from "./ComfyTextNode" export { default as ComfyMultiRegionNode } from "./ComfyMultiRegionNode" +export { default as ComfyMarkdownNode } from "./ComfyMarkdownNode" diff --git a/src/lib/widgets/ImageUploadWidget.svelte b/src/lib/widgets/ImageUploadWidget.svelte index b06bc3a..4f9c3cf 100644 --- a/src/lib/widgets/ImageUploadWidget.svelte +++ b/src/lib/widgets/ImageUploadWidget.svelte @@ -52,12 +52,19 @@ }; let hasImage = false; - $: hasImage = $nodeValue && $nodeValue.length > 0; $: if (!hasImage) { editMask = false; } + let mask: ComfyImageLocation | null; + $: if (hasImage && canMask) { + mask = $nodeValue[0].children?.find(i => i.tags.includes("mask"))?.comfyUIFile; + } + else { + mask = null; + } + const MASK_FILENAME: string = "ComfyBoxMask.png" async function onMaskReleased(e: CustomEvent) { @@ -122,6 +129,7 @@ // TODO other child image types preserved here? image.children = []; } + mask = null; if (maskCanvasComp) { maskCanvasComp.clearStrokes(); } @@ -232,6 +240,7 @@ + import { type WidgetLayout } from "$lib/stores/layoutStates"; + import { get, type Writable, writable } from "svelte/store"; + import { Block } from "@gradio/atoms"; + import type { ComfyMarkdownNode } from "$lib/nodes/widgets"; + import SvelteMarkdown from "@dogagenc/svelte-markdown" + import NullMarkdownRenderer from "./markdown/NullMarkdownRenderer.svelte" + import { SvelteComponentDev } from "svelte/internal"; + + export let widget: WidgetLayout | null = null; + export let isMobile: boolean = false; + + let node: ComfyMarkdownNode | null = null; + let nodeValue: Writable = writable(""); + let attrsChanged: Writable = writable(0); + + let renderers: Record = { + "html": NullMarkdownRenderer + } + + $: widget && setNodeValue(widget); + + function setNodeValue(widget: WidgetLayout) { + if (widget) { + node = widget.node as ComfyMarkdownNode + nodeValue = node.value; + attrsChanged = widget.attrsChanged; + } + }; + + +
+ {#key $attrsChanged} + {#if widget !== null && node !== null} + + + + {/if} + {/key} +
+ + diff --git a/src/lib/widgets/markdown/NullMarkdownRenderer.svelte b/src/lib/widgets/markdown/NullMarkdownRenderer.svelte new file mode 100644 index 0000000..70f162e --- /dev/null +++ b/src/lib/widgets/markdown/NullMarkdownRenderer.svelte @@ -0,0 +1,7 @@ + + +