Workflow templates (#938)

* Add template gallery

* Add simple test

* Add examples

* Enable floating menu in test
This commit is contained in:
pythongosssss
2024-09-24 08:59:28 +09:00
committed by Chenlei Hu
parent 2aaee5c331
commit bf7652227a
15 changed files with 2016 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ import * as fs from 'fs'
import { NodeBadgeMode } from '../src/types/nodeSource'
import { NodeId } from '../src/types/comfyWorkflow'
import { ManageGroupNode } from './helpers/manageGroupNode'
import { ComfyTemplates } from './helpers/templates'
interface Position {
x: number
@@ -182,6 +183,10 @@ class WorkflowsSidebarTab extends SidebarTab {
super(page, 'workflows')
}
get browseGalleryButton() {
return this.page.locator('.browse-templates-button')
}
get newBlankWorkflowButton() {
return this.page.locator('.new-blank-workflow-button')
}
@@ -297,6 +302,7 @@ export class ComfyPage {
public readonly searchBox: ComfyNodeSearchBox
public readonly menu: ComfyMenu
public readonly appMenu: ComfyAppMenu
public readonly templates: ComfyTemplates
constructor(
public readonly page: Page,
@@ -311,6 +317,7 @@ export class ComfyPage {
this.searchBox = new ComfyNodeSearchBox(page)
this.menu = new ComfyMenu(page)
this.appMenu = new ComfyAppMenu(page)
this.templates = new ComfyTemplates(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {

View File

@@ -0,0 +1,12 @@
import { Locator, Page } from '@playwright/test'
export class ComfyTemplates {
readonly content: Locator
constructor(readonly page: Page) {
this.content = page.getByTestId('template-workflows-content')
}
async loadTemplate(id: string) {
await this.content.getByTestId(`template-workflow-${id}`).click()
}
}

View File

@@ -0,0 +1,34 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Templates', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Floating')
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test('Can load template workflows', async ({ comfyPage }) => {
// This test will need expanding on once the templates are decided
// Clear the workflow
await comfyPage.menu.workflowsTab.open()
await comfyPage.menu.workflowsTab.newBlankWorkflowButton.click()
await expect(async () => {
expect(await comfyPage.getGraphNodesCount()).toBe(0)
}).toPass({ timeout: 250 })
// Load a template
await comfyPage.menu.workflowsTab.browseGalleryButton.click()
await expect(comfyPage.templates.content).toBeVisible()
await comfyPage.templates.loadTemplate('default')
await expect(comfyPage.templates.content).toBeHidden()
// Ensure we now have some nodes
await expect(async () => {
expect(await comfyPage.getGraphNodesCount()).toBeGreaterThan(0)
}).toPass({ timeout: 250 })
})
})

View File

@@ -0,0 +1,351 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": [
425.27801513671875,
180.6060791015625
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": [
422.84503173828125,
164.31304931640625
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": [
315,
106
],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
512,
512,
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": [
315,
262
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
156680208700286,
true,
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": [
210,
46
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": [
210,
26
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {}
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": [
315,
98
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {},
"widgets_values": [
"v1-5-pruned-emaonly.ckpt"
]
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
9,
8,
0,
9,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

View File

@@ -0,0 +1,413 @@
{
"last_node_id": 36,
"last_link_id": 58,
"nodes": [
{
"id": 33,
"type": "CLIPTextEncode",
"pos": [
390,
400
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {
"collapsed": true
},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 54,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
55
],
"slot_index": 0
}
],
"title": "CLIP Text Encode (Negative Prompt)",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 27,
"type": "EmptySD3LatentImage",
"pos": [
471,
455
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
51
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptySD3LatentImage"
},
"widgets_values": [
1024,
1024,
1
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1151,
195
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 52
},
{
"name": "vae",
"type": "VAE",
"link": 46
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1375,
194
],
"size": {
"0": 985.3012084960938,
"1": 1060.3828125
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 31,
"type": "KSampler",
"pos": [
816,
192
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 47
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 58
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 55
},
{
"name": "latent_image",
"type": "LATENT",
"link": 51
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
52
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
173805153958730,
"randomize",
4,
1,
"euler",
"simple",
1
]
},
{
"id": 30,
"type": "CheckpointLoaderSimple",
"pos": [
48,
192
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
47
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
45,
54
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
46
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"flux1-schnell-fp8.safetensors"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
384,
192
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 45
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
58
],
"slot_index": 0
}
],
"title": "CLIP Text Encode (Positive Prompt)",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 34,
"type": "Note",
"pos": [
831,
501
],
"size": {
"0": 282.8617858886719,
"1": 164.08004760742188
},
"flags": {},
"order": 2,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"Note that Flux dev and schnell do not have any negative prompt so CFG should be set to 1.0. Setting CFG to 1.0 means the negative prompt is ignored.\n\nThe schnell model is a distilled model that can generate a good image with only 4 steps."
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
45,
30,
1,
6,
0,
"CLIP"
],
[
46,
30,
2,
8,
1,
"VAE"
],
[
47,
30,
0,
31,
0,
"MODEL"
],
[
51,
27,
0,
31,
3,
"LATENT"
],
[
52,
31,
0,
8,
0,
"LATENT"
],
[
54,
30,
1,
33,
0,
"CLIP"
],
[
55,
33,
0,
31,
2,
"CONDITIONING"
],
[
58,
6,
0,
31,
1,
"CONDITIONING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.1,
"offset": [
0.6836674124529055,
1.8290357611967831
]
}
},
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

View File

@@ -0,0 +1,442 @@
{
"last_node_id": 14,
"last_link_id": 17,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 15
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"watermark, text\n"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 14
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"photograph of victorian woman with wings, sky clouds, meadow grass\n"
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 17
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 10,
"type": "LoadImage",
"pos": [
215.9799597167969,
703.6800268554688
],
"size": [
315,
314.00002670288086
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10
],
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 12,
"type": "VAEEncode",
"pos": [
614.979959716797,
707.6800268554688
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 10
},
{
"name": "vae",
"type": "VAE",
"link": 16
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
11
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEEncode"
}
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 13
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 11
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
280823642470253,
"randomize",
20,
8,
"dpmpp_2m",
"normal",
0.8700000000000001
]
},
{
"id": 14,
"type": "CheckpointLoaderSimple",
"pos": [
19,
433
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
13
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
14,
15
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
16,
17
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v1-5-pruned-emaonly.ckpt"
]
}
],
"links": [
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
10,
0,
12,
0,
"IMAGE"
],
[
11,
12,
0,
3,
3,
"LATENT"
],
[
13,
14,
0,
3,
0,
"MODEL"
],
[
14,
14,
1,
6,
0,
"CLIP"
],
[
15,
14,
1,
7,
0,
"CLIP"
],
[
16,
14,
2,
12,
1,
"VAE"
],
[
17,
14,
2,
8,
1,
"VAE"
]
],
"groups": [
{
"title": "Loading images",
"bounding": [
150,
630,
726,
171
],
"color": "#3f789e"
}
],
"config": {},
"extra": {},
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -0,0 +1,645 @@
{
"last_node_id": 16,
"last_link_id": 23,
"nodes": [
{
"id": 8,
"type": "VAEDecode",
"pos": [
1235.7215957031258,
577.1878720703122
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 21
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 10,
"type": "LatentUpscale",
"pos": [
1238,
170
],
"size": {
"0": 315,
"1": 130
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 10
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
14
]
}
],
"properties": {
"Node name for S&R": "LatentUpscale"
},
"widgets_values": [
"nearest-exact",
1152,
1152,
"disabled"
]
},
{
"id": 13,
"type": "VAEDecode",
"pos": [
1961,
125
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 15
},
{
"name": "vae",
"type": "VAE",
"link": 22
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
17
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
374,
171
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 19
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4,
12
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"masterpiece HDR victorian portrait painting of woman, blonde hair, mountain nature, blue sky\n"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
377,
381
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 20
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6,
13
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"bad hands, text, watermark\n"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
435,
600
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
768,
768,
1
]
},
{
"id": 11,
"type": "KSampler",
"pos": [
1585,
114
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 23,
"slot_index": 0
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 12,
"slot_index": 1
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 13,
"slot_index": 2
},
{
"name": "latent_image",
"type": "LATENT",
"link": 14,
"slot_index": 3
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
15
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
469771404043268,
"randomize",
14,
8,
"dpmpp_2m",
"simple",
0.5
]
},
{
"id": 12,
"type": "SaveImage",
"pos": [
2203,
123
],
"size": {
"0": 407.53717041015625,
"1": 468.13226318359375
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 17
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
845,
172
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 18
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7,
10
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
89848141647836,
"randomize",
12,
8,
"dpmpp_sde",
"normal",
1
]
},
{
"id": 16,
"type": "CheckpointLoaderSimple",
"pos": [
24,
315
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
18,
23
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
19,
20
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
21,
22
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v2-1_768-ema-pruned.ckpt"
]
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1495.7215957031258,
576.1878720703122
],
"size": [
232.9403301043692,
282.4336258387117
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
}
],
"links": [
[
2,
5,
0,
3,
3,
"LATENT"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
3,
0,
10,
0,
"LATENT"
],
[
12,
6,
0,
11,
1,
"CONDITIONING"
],
[
13,
7,
0,
11,
2,
"CONDITIONING"
],
[
14,
10,
0,
11,
3,
"LATENT"
],
[
15,
11,
0,
13,
0,
"LATENT"
],
[
17,
13,
0,
12,
0,
"IMAGE"
],
[
18,
16,
0,
3,
0,
"MODEL"
],
[
19,
16,
1,
6,
0,
"CLIP"
],
[
20,
16,
1,
7,
0,
"CLIP"
],
[
21,
16,
2,
8,
1,
"VAE"
],
[
22,
16,
2,
13,
1,
"VAE"
],
[
23,
16,
0,
11,
0,
"MODEL"
]
],
"groups": [
{
"title": "Txt2Img",
"bounding": [
-1,
30,
1211,
708
],
"color": "#a1309b"
},
{
"title": "Save Intermediate Image",
"bounding": [
1225,
500,
516,
196
],
"color": "#3f789e"
},
{
"title": "Hires Fix",
"bounding": [
1224,
29,
710,
464
],
"color": "#b58b2a"
},
{
"title": "Save Final Image",
"bounding": [
1949,
31,
483,
199
],
"color": "#3f789e"
}
],
"config": {},
"extra": {},
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

View File

@@ -1,6 +1,13 @@
<template>
<SidebarTabTemplate :title="$t('sideToolbar.workflows')">
<template #tool-buttons>
<Button
class="browse-templates-button"
icon="pi pi-th-large"
v-tooltip="$t('sideToolbar.browseTemplates')"
text
@click="browseTemplates"
/>
<Button
class="browse-workflows-button"
icon="pi pi-folder-open"
@@ -112,6 +119,7 @@ import { TreeExplorerNode } from '@/types/treeExplorerTypes'
import { ComfyWorkflow } from '@/scripts/workflows'
import { useI18n } from 'vue-i18n'
import { useTreeExpansion } from '@/hooks/treeHooks'
import { showTemplateWorkflowsDialog } from '@/services/dialogService'
const searchQuery = ref('')
const isSearching = computed(() => searchQuery.value.length > 0)
@@ -145,6 +153,10 @@ const browse = () => {
app.ui.loadFile()
}
const browseTemplates = () => {
showTemplateWorkflowsDialog()
}
const createBlank = () => {
app.workflowManager.setWorkflow(null)
app.clean()

View File

@@ -0,0 +1,81 @@
<template>
<div
class="flex flex-wrap content-around justify-around gap-4"
data-testid="template-workflows-content"
>
<div
v-for="template in templates"
:key="template"
:data-testid="`template-workflow-${template}`"
>
<Card>
<template #header>
<div
class="relative overflow-hidden rounded-lg cursor-pointer"
@click="loadWorkflow(template)"
>
<img
:src="`/templates/${template}.png`"
class="w-64 h-64 rounded-lg object-cover"
/>
<a>
<div
class="absolute top-0 left-0 w-64 h-64 overflow-hidden opacity-0 transition duration-300 ease-in-out hover:opacity-100 bg-opacity-50 bg-black flex items-center justify-center"
>
<i class="pi pi-play-circle"></i>
</div>
</a>
<ProgressSpinner
v-if="loading === template"
class="absolute inset-0 z-1 w-3/12 h-full"
/>
</div>
</template>
<template #subtitle>{{
$t(`templateWorkflows.template.${template}`)
}}</template>
</Card>
</div>
</div>
</template>
<script setup lang="ts">
import { useDialogStore } from '@/stores/dialogStore'
import Card from 'primevue/card'
import ProgressSpinner from 'primevue/progressspinner'
import { ref } from 'vue'
import { app } from '@/scripts/app'
import { api } from '@/scripts/api'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const templates = ['default', 'image2image', 'upscale', 'flux_schnell']
const loading = ref<string | null>(null)
const loadWorkflow = async (id: string) => {
loading.value = id
const json = await fetch(api.fileURL(`templates/${id}.json`)).then((r) =>
r.json()
)
useDialogStore().closeDialog()
await app.loadGraphData(
json,
true,
true,
t(`templateWorkflows.template.${id}`)
)
return false
}
</script>
<style lang="css" scoped>
.p-card {
--p-card-body-padding: 10px 0 0 0;
overflow: hidden;
}
:deep(.p-card-subtitle) {
text-align: center;
}
</style>

View File

@@ -50,6 +50,7 @@ const messages = {
queue: 'Queue',
nodeLibrary: 'Node Library',
workflows: 'Workflows',
browseTemplates: 'Browse example templates',
nodeLibraryTab: {
sortOrder: 'Sort Order'
},
@@ -80,6 +81,15 @@ const messages = {
clipspace: 'Open Clipspace',
resetView: 'Reset canvas view',
clear: 'Clear workflow'
},
templateWorkflows: {
title: 'Get Started with a Template',
template: {
default: 'Image Generation',
image2image: 'Image to Image',
upscale: '2 Pass Upscale',
flux_schnell: 'Flux Schnell'
}
}
},
zh: {

View File

@@ -8,6 +8,8 @@ import SettingDialogContent from '@/components/dialog/content/SettingDialogConte
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
import type { ExecutionErrorWsMessage } from '@/types/apiTypes'
import ExecutionErrorDialogContent from '@/components/dialog/content/ExecutionErrorDialogContent.vue'
import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsContent.vue'
import { i18n } from '@/i18n'
export function showLoadWorkflowWarning(props: {
missingNodeTypes: any[]
@@ -47,3 +49,10 @@ export function showExecutionErrorDialog(error: ExecutionErrorWsMessage) {
}
})
}
export function showTemplateWorkflowsDialog() {
useDialogStore().showDialog({
title: i18n.global.t('templateWorkflows.title'),
component: TemplateWorkflowsContent
})
}