From 80097007c981cf6cee5a5222890ea61b4b007695 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 15:03:02 -0700 Subject: [PATCH 1/5] refactor readme to suggest deployed reports (#5112) --- browser_tests/README.md | 39 ++++++++------------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/browser_tests/README.md b/browser_tests/README.md index 91f2f0a46b..ede6a303a9 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -392,16 +392,6 @@ Option 2 - Generate local baselines for comparison: npx playwright test --update-snapshots ``` -### Getting Test Artifacts from GitHub Actions - -When tests fail in CI, you can download screenshots and traces: - -1. Go to the failed workflow run in GitHub Actions -2. Scroll to "Artifacts" section at the bottom -3. Download `playwright-report` or `test-results` -4. Extract and open the HTML report locally -5. View actual vs expected screenshots and execution traces - ### Creating New Screenshot Baselines For PRs from `Comfy-Org/ComfyUI_frontend` branches: @@ -412,17 +402,19 @@ For PRs from `Comfy-Org/ComfyUI_frontend` branches: > **Note:** Fork PRs cannot auto-commit screenshots. A maintainer will need to commit the screenshots manually for you (don't worry, they'll do it). -## CI/CD Integration +## Viewing Test Reports ### Automated Test Deployment -The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches. This provides: +The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches. -- **Live test reports** with interactive HTML views -- **Cross-browser testing** across chromium, mobile-chrome, and different viewport sizes -- **Real-time PR comments** with test status and links to detailed reports +### Accessing Test Reports -#### How it works: +- **From PR comments**: Click the "View Report" links for each browser +- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments) +- **From GitHub Actions**: Download artifacts from failed runs + +### How It Works 1. **Test execution**: All browser tests run in parallel across multiple browsers 2. **Report generation**: HTML reports are generated for each browser configuration @@ -437,21 +429,6 @@ The project automatically deploys Playwright test reports to Cloudflare Pages fo - Direct links to interactive test reports - Real-time progress updates as tests complete -#### Accessing test reports: - -- **From PR comments**: Click the "View Report" links for each browser -- **From GitHub Actions**: Download artifacts from failed runs -- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments) - -#### Report features: - -- **Interactive HTML reports** with test results, screenshots, and traces -- **Detailed failure analysis** with before/after screenshots -- **Test execution videos** for failed tests -- **Network logs** and console output for debugging - -This integration ensures that test results are easily accessible to reviewers and maintainers, making it simple to verify that changes don't break existing functionality across different browsers and viewport sizes. - ## Resources - [Playwright UI Mode](https://playwright.dev/docs/test-ui-mode) - Interactive test debugging From 97e5291b05bc48eb2beb5d7b6298f3f2664071ca Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:14:06 +0100 Subject: [PATCH 2/5] Update to latest version of workflow icon (#5103) --- src/assets/icons/custom/workflow.svg | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/assets/icons/custom/workflow.svg b/src/assets/icons/custom/workflow.svg index 72f90c1a4f..043d24e7b5 100644 --- a/src/assets/icons/custom/workflow.svg +++ b/src/assets/icons/custom/workflow.svg @@ -1,7 +1,3 @@ - - - - - - + + From 8d0a523ffd3cffec6931389688be2d666ac38ae4 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 18:36:00 -0700 Subject: [PATCH 3/5] [refactor] Remove obsolete Kontext Edit Button (#5108) * feat: Remove obsolete Kontext Edit Button Removes the 'Kontext Edit Button' and its associated code, as it has been made obsolete by the new 'Subgraphs + Partial Execution' feature. Fixes #5093 * Update locales [skip ci] --------- Co-authored-by: github-actions --- src/components/graph/SelectionToolbox.vue | 2 - .../selectionToolbox/EditModelButton.vue | 37 - src/composables/useCoreCommands.ts | 12 - src/locales/ar/commands.json | 3 - src/locales/ar/main.json | 1 - src/locales/en/commands.json | 3 - src/locales/en/main.json | 1 - src/locales/es/commands.json | 3 - src/locales/es/main.json | 1 - src/locales/fr/commands.json | 3 - src/locales/fr/main.json | 1 - src/locales/ja/commands.json | 3 - src/locales/ja/main.json | 1 - src/locales/ko/commands.json | 3 - src/locales/ko/main.json | 1 - src/locales/ru/commands.json | 3 - src/locales/ru/main.json | 1 - src/locales/zh-TW/commands.json | 3 - src/locales/zh-TW/main.json | 1 - src/locales/zh/commands.json | 3 - src/locales/zh/main.json | 1 - src/scripts/fluxKontextEditNode.ts | 693 ------------------ 22 files changed, 780 deletions(-) delete mode 100644 src/components/graph/selectionToolbox/EditModelButton.vue delete mode 100644 src/scripts/fluxKontextEditNode.ts diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue index d4cf408fcf..bbbeadef94 100644 --- a/src/components/graph/SelectionToolbox.vue +++ b/src/components/graph/SelectionToolbox.vue @@ -12,7 +12,6 @@ - @@ -35,7 +34,6 @@ import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue' import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue' import ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue' import DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue' -import EditModelButton from '@/components/graph/selectionToolbox/EditModelButton.vue' import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue' import ExtensionCommandButton from '@/components/graph/selectionToolbox/ExtensionCommandButton.vue' import HelpButton from '@/components/graph/selectionToolbox/HelpButton.vue' diff --git a/src/components/graph/selectionToolbox/EditModelButton.vue b/src/components/graph/selectionToolbox/EditModelButton.vue deleted file mode 100644 index d9f515ddd4..0000000000 --- a/src/components/graph/selectionToolbox/EditModelButton.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 80eeca78f7..fe6d112bba 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -16,7 +16,6 @@ import { import { Point } from '@/lib/litegraph/src/litegraph' import { api } from '@/scripts/api' import { app } from '@/scripts/app' -import { addFluxKontextGroupNode } from '@/scripts/fluxKontextEditNode' import { useDialogService } from '@/services/dialogService' import { useLitegraphService } from '@/services/litegraphService' import { useWorkflowService } from '@/services/workflowService' @@ -775,17 +774,6 @@ export function useCoreCommands(): ComfyCommand[] { versionAdded: moveSelectedNodesVersionAdded, function: () => moveSelectedNodes(([x, y], gridSize) => [x + gridSize, y]) }, - { - id: 'Comfy.Canvas.AddEditModelStep', - icon: 'pi pi-pen-to-square', - label: 'Add Edit Model Step', - versionAdded: '1.23.3', - function: async () => { - const node = app.canvas.selectedItems.values().next().value - if (!(node instanceof LGraphNode)) return - await addFluxKontextGroupNode(node) - } - }, { id: 'Comfy.Graph.ConvertToSubgraph', icon: 'pi pi-sitemap', diff --git a/src/locales/ar/commands.json b/src/locales/ar/commands.json index 760d0a1780..95b417dc1d 100644 --- a/src/locales/ar/commands.json +++ b/src/locales/ar/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "تصفح القوالب" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "إضافة خطوة تحرير النموذج" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "حذف العناصر المحددة" }, diff --git a/src/locales/ar/main.json b/src/locales/ar/main.json index af0549674a..8a9cc8b2cf 100644 --- a/src/locales/ar/main.json +++ b/src/locales/ar/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "حول ComfyUI", - "Add Edit Model Step": "إضافة خطوة تعديل النموذج", "Bottom Panel": "لوحة سفلية", "Browse Templates": "تصفح القوالب", "Bypass/Unbypass Selected Nodes": "تجاوز/إلغاء تجاوز العقد المحددة", diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 5b0d9c445a..8508497e66 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Browse Templates" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Add Edit Model Step" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Delete Selected Items" }, diff --git a/src/locales/en/main.json b/src/locales/en/main.json index b4c2537082..b6ed989a14 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -958,7 +958,6 @@ "Restart": "Restart", "Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node", "Browse Templates": "Browse Templates", - "Add Edit Model Step": "Add Edit Model Step", "Delete Selected Items": "Delete Selected Items", "Zoom to fit": "Zoom to fit", "Move Selected Nodes Down": "Move Selected Nodes Down", diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index 580515847e..d41962c03b 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Explorar plantillas" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Agregar paso de edición de modelo" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Eliminar elementos seleccionados" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index e056b0afb3..ef27fc8c28 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "Acerca de ComfyUI", - "Add Edit Model Step": "Agregar paso de edición de modelo", "Bottom Panel": "Panel inferior", "Browse Templates": "Explorar plantillas", "Bypass/Unbypass Selected Nodes": "Evitar/No evitar nodos seleccionados", diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 69a9f4fc32..e04e372b41 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Parcourir les modèles" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Ajouter/Modifier une étape de modèle" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Supprimer les éléments sélectionnés" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 3680db24eb..31dcbe86f6 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "À propos de ComfyUI", - "Add Edit Model Step": "Ajouter une étape d’édition de modèle", "Bottom Panel": "Panneau inférieur", "Browse Templates": "Parcourir les modèles", "Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés", diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index caaf7732d0..307ef72e56 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "テンプレートを参照" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "編集モデルステップを追加" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "選択したアイテムを削除" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 34caa47a24..c3af5ee251 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "ComfyUIについて", - "Add Edit Model Step": "モデル編集ステップを追加", "Bottom Panel": "下部パネル", "Browse Templates": "テンプレートを参照", "Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除", diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 7105cd8212..431ae7ae00 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "템플릿 탐색" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "모델 편집 단계 추가" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "선택한 항목 삭제" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 672fad4fca..f11bc201f0 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "ComfyUI에 대하여", - "Add Edit Model Step": "모델 편집 단계 추가", "Bottom Panel": "하단 패널", "Browse Templates": "템플릿 탐색", "Bypass/Unbypass Selected Nodes": "선택한 노드 우회/우회 해제", diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index fa3927190b..86fc88f176 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Просмотр шаблонов" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Добавить или изменить шаг модели" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Удалить выбранные элементы" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index 3257bc1b11..04e2b2bef8 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "О ComfyUI", - "Add Edit Model Step": "Добавить или изменить шаг модели", "Bottom Panel": "Нижняя панель", "Browse Templates": "Просмотреть шаблоны", "Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные ноды", diff --git a/src/locales/zh-TW/commands.json b/src/locales/zh-TW/commands.json index 71d1907b6f..46fbcb30d5 100644 --- a/src/locales/zh-TW/commands.json +++ b/src/locales/zh-TW/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "瀏覽範本" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "新增編輯模型步驟" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "刪除選取項目" }, diff --git a/src/locales/zh-TW/main.json b/src/locales/zh-TW/main.json index b61ea22cc5..fe0d5f4168 100644 --- a/src/locales/zh-TW/main.json +++ b/src/locales/zh-TW/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "關於 ComfyUI", - "Add Edit Model Step": "新增編輯模型步驟", "Bottom Panel": "底部面板", "Browse Templates": "瀏覽範本", "Bypass/Unbypass Selected Nodes": "繞過/取消繞過選取節點", diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index a42111c547..5d319aa170 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "浏览模板" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "添加编辑模型步骤" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "删除选定的项目" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 6c678c651a..19b6533e82 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "关于ComfyUI", - "Add Edit Model Step": "添加编辑模型步骤", "Bottom Panel": "底部面板", "Browse Templates": "浏览模板", "Bypass/Unbypass Selected Nodes": "忽略/取消忽略选定节点", diff --git a/src/scripts/fluxKontextEditNode.ts b/src/scripts/fluxKontextEditNode.ts deleted file mode 100644 index fe794d43cc..0000000000 --- a/src/scripts/fluxKontextEditNode.ts +++ /dev/null @@ -1,693 +0,0 @@ -import _ from 'es-toolkit/compat' - -import { - type INodeOutputSlot, - type LGraph, - type LGraphNode, - LLink, - LiteGraph, - type Point -} from '@/lib/litegraph/src/litegraph' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' -import { parseFilePath } from '@/utils/formatUtil' - -import { app } from './app' - -const fluxKontextGroupNode = { - nodes: [ - { - id: -1, - type: 'Reroute', - pos: [2354.87890625, -127.23468780517578], - size: [75, 26], - flags: {}, - order: 20, - mode: 0, - inputs: [{ name: '', type: '*', link: null }], - outputs: [{ name: '', type: '*', links: null }], - properties: { showOutputText: false, horizontal: false }, - index: 0 - }, - { - id: -1, - type: 'ReferenceLatent', - pos: [2730, -220], - size: [197.712890625, 46], - flags: {}, - order: 22, - mode: 0, - inputs: [ - { - localized_name: 'conditioning', - name: 'conditioning', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'latent', - name: 'latent', - shape: 7, - type: 'LATENT', - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - links: [] - } - ], - properties: { - 'Node name for S&R': 'ReferenceLatent', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - index: 1 - }, - { - id: -1, - type: 'VAEDecode', - pos: [3270, -110], - size: [210, 46], - flags: {}, - order: 25, - mode: 0, - inputs: [ - { - localized_name: 'samples', - name: 'samples', - type: 'LATENT', - link: null - }, - { - localized_name: 'vae', - name: 'vae', - type: 'VAE', - link: null - } - ], - outputs: [ - { - localized_name: 'IMAGE', - name: 'IMAGE', - type: 'IMAGE', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'VAEDecode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - index: 2 - }, - { - id: -1, - type: 'KSampler', - pos: [2930, -110], - size: [315, 262], - flags: {}, - order: 24, - mode: 0, - inputs: [ - { - localized_name: 'model', - name: 'model', - type: 'MODEL', - link: null - }, - { - localized_name: 'positive', - name: 'positive', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'negative', - name: 'negative', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'latent_image', - name: 'latent_image', - type: 'LATENT', - link: null - }, - { - localized_name: 'seed', - name: 'seed', - type: 'INT', - widget: { name: 'seed' }, - link: null - }, - { - localized_name: 'steps', - name: 'steps', - type: 'INT', - widget: { name: 'steps' }, - link: null - }, - { - localized_name: 'cfg', - name: 'cfg', - type: 'FLOAT', - widget: { name: 'cfg' }, - link: null - }, - { - localized_name: 'sampler_name', - name: 'sampler_name', - type: 'COMBO', - widget: { name: 'sampler_name' }, - link: null - }, - { - localized_name: 'scheduler', - name: 'scheduler', - type: 'COMBO', - widget: { name: 'scheduler' }, - link: null - }, - { - localized_name: 'denoise', - name: 'denoise', - type: 'FLOAT', - widget: { name: 'denoise' }, - link: null - } - ], - outputs: [ - { - localized_name: 'LATENT', - name: 'LATENT', - type: 'LATENT', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'KSampler', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [972054013131369, 'fixed', 20, 1, 'euler', 'simple', 1], - index: 3 - }, - { - id: -1, - type: 'FluxGuidance', - pos: [2940, -220], - size: [211.60000610351562, 58], - flags: {}, - order: 23, - mode: 0, - inputs: [ - { - localized_name: 'conditioning', - name: 'conditioning', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'guidance', - name: 'guidance', - type: 'FLOAT', - widget: { name: 'guidance' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'FluxGuidance', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [2.5], - index: 4 - }, - { - id: -1, - type: 'SaveImage', - pos: [3490, -110], - size: [985.3012084960938, 1060.3828125], - flags: {}, - order: 26, - mode: 0, - inputs: [ - { - localized_name: 'images', - name: 'images', - type: 'IMAGE', - link: null - }, - { - localized_name: 'filename_prefix', - name: 'filename_prefix', - type: 'STRING', - widget: { name: 'filename_prefix' }, - link: null - } - ], - outputs: [], - properties: { cnr_id: 'comfy-core', ver: '0.3.38' }, - widgets_values: ['ComfyUI'], - index: 5 - }, - { - id: -1, - type: 'CLIPTextEncode', - pos: [2500, -110], - size: [422.84503173828125, 164.31304931640625], - flags: {}, - order: 12, - mode: 0, - inputs: [ - { - localized_name: 'clip', - name: 'clip', - type: 'CLIP', - link: null - }, - { - localized_name: 'text', - name: 'text', - type: 'STRING', - widget: { name: 'text' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - title: 'CLIP Text Encode (Positive Prompt)', - properties: { - 'Node name for S&R': 'CLIPTextEncode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['there is a bright light'], - color: '#232', - bgcolor: '#353', - index: 6 - }, - { - id: -1, - type: 'CLIPTextEncode', - pos: [2504.1435546875, 97.9598617553711], - size: [422.84503173828125, 164.31304931640625], - flags: { collapsed: true }, - order: 13, - mode: 0, - inputs: [ - { - localized_name: 'clip', - name: 'clip', - type: 'CLIP', - link: null - }, - { - localized_name: 'text', - name: 'text', - type: 'STRING', - widget: { name: 'text' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - title: 'CLIP Text Encode (Negative Prompt)', - properties: { - 'Node name for S&R': 'CLIPTextEncode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [''], - color: '#322', - bgcolor: '#533', - index: 7 - }, - { - id: -1, - type: 'UNETLoader', - pos: [2630, -370], - size: [270, 82], - flags: {}, - order: 6, - mode: 0, - inputs: [ - { - localized_name: 'unet_name', - name: 'unet_name', - type: 'COMBO', - widget: { name: 'unet_name' }, - link: null - }, - { - localized_name: 'weight_dtype', - name: 'weight_dtype', - type: 'COMBO', - widget: { name: 'weight_dtype' }, - link: null - } - ], - outputs: [ - { - localized_name: 'MODEL', - name: 'MODEL', - type: 'MODEL', - links: [] - } - ], - properties: { - 'Node name for S&R': 'UNETLoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['flux1-kontext-dev.safetensors', 'default'], - color: '#223', - bgcolor: '#335', - index: 8 - }, - { - id: -1, - type: 'DualCLIPLoader', - pos: [2100, -290], - size: [337.76861572265625, 130], - flags: {}, - order: 8, - mode: 0, - inputs: [ - { - localized_name: 'clip_name1', - name: 'clip_name1', - type: 'COMBO', - widget: { name: 'clip_name1' }, - link: null - }, - { - localized_name: 'clip_name2', - name: 'clip_name2', - type: 'COMBO', - widget: { name: 'clip_name2' }, - link: null - }, - { - localized_name: 'type', - name: 'type', - type: 'COMBO', - widget: { name: 'type' }, - link: null - }, - { - localized_name: 'device', - name: 'device', - shape: 7, - type: 'COMBO', - widget: { name: 'device' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CLIP', - name: 'CLIP', - type: 'CLIP', - links: [] - } - ], - properties: { - 'Node name for S&R': 'DualCLIPLoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [ - 'clip_l.safetensors', - 't5xxl_fp8_e4m3fn_scaled.safetensors', - 'flux', - 'default' - ], - color: '#223', - bgcolor: '#335', - index: 9 - }, - { - id: -1, - type: 'VAELoader', - pos: [2960, -370], - size: [270, 58], - flags: {}, - order: 7, - mode: 0, - inputs: [ - { - localized_name: 'vae_name', - name: 'vae_name', - type: 'COMBO', - widget: { name: 'vae_name' }, - link: null - } - ], - outputs: [ - { - localized_name: 'VAE', - name: 'VAE', - type: 'VAE', - links: [] - } - ], - properties: { - 'Node name for S&R': 'VAELoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['ae.safetensors'], - color: '#223', - bgcolor: '#335', - index: 10 - } - ], - links: [ - [6, 0, 1, 0, 72, 'CONDITIONING'], - [0, 0, 1, 1, 66, '*'], - [3, 0, 2, 0, 69, 'LATENT'], - [10, 0, 2, 1, 76, 'VAE'], - [8, 0, 3, 0, 74, 'MODEL'], - [4, 0, 3, 1, 70, 'CONDITIONING'], - [7, 0, 3, 2, 73, 'CONDITIONING'], - [0, 0, 3, 3, 66, '*'], - [1, 0, 4, 0, 67, 'CONDITIONING'], - [2, 0, 5, 0, 68, 'IMAGE'], - [9, 0, 6, 0, 75, 'CLIP'], - [9, 0, 7, 0, 75, 'CLIP'] - ], - external: [], - config: { - '0': {}, - '1': {}, - '2': { output: { '0': { visible: true } } }, - '3': { - output: { '0': { visible: true } }, - input: { - denoise: { visible: false }, - cfg: { visible: false } - } - }, - '4': {}, - '5': {}, - '6': {}, - '7': { input: { text: { visible: false } } }, - '8': { input: { weight_dtype: { visible: false } } }, - '9': { input: { type: { visible: false }, device: { visible: false } } }, - '10': {} - } -} - -export async function ensureGraphHasFluxKontextGroupNode( - graph: LGraph & { extra: { groupNodes?: Record } } -) { - graph.extra ??= {} - graph.extra.groupNodes ??= {} - if (graph.extra.groupNodes['FLUX.1 Kontext Image Edit']) return - - graph.extra.groupNodes['FLUX.1 Kontext Image Edit'] = - structuredClone(fluxKontextGroupNode) - - // Lazy import to avoid circular dependency issues - const { GroupNodeConfig } = await import('@/extensions/core/groupNode') - await GroupNodeConfig.registerFromWorkflow( - { - 'FLUX.1 Kontext Image Edit': - graph.extra.groupNodes['FLUX.1 Kontext Image Edit'] - }, - [] - ) -} - -export async function addFluxKontextGroupNode(fromNode: LGraphNode) { - const { canvas } = app - const { graph } = canvas - if (!graph) throw new TypeError('Graph is not initialized') - await ensureGraphHasFluxKontextGroupNode(graph) - - const node = LiteGraph.createNode('workflow>FLUX.1 Kontext Image Edit') - if (!node) throw new TypeError('Failed to create node') - - const pos = getPosToRightOfNode(fromNode) - - graph.add(node) - node.pos = pos - app.canvas.processSelect(node, undefined) - - connectPreviousLatent(fromNode, node) - - const symb = Object.getOwnPropertySymbols(node)[0] - // @ts-expect-error It's there -- promise. - node[symb].populateWidgets() - - setWidgetValues(node) -} - -function setWidgetValues(node: LGraphNode) { - const seedInput = node.widgets?.find((x) => x.name === 'seed') - if (!seedInput) throw new TypeError('Seed input not found') - seedInput.value = Math.floor(Math.random() * 1_125_899_906_842_624) - - const firstClip = node.widgets?.find((x) => x.name === 'clip_name1') - setPreferredValue('t5xxl_fp8_e4m3fn_scaled.safetensors', 't5xxl', firstClip) - - const secondClip = node.widgets?.find((x) => x.name === 'clip_name2') - setPreferredValue('clip_l.safetensors', 'clip_l', secondClip) - - const unet = node.widgets?.find((x) => x.name === 'unet_name') - setPreferredValue('flux1-dev-kontext_fp8_scaled.safetensors', 'kontext', unet) - - const vae = node.widgets?.find((x) => x.name === 'vae_name') - setPreferredValue('ae.safetensors', 'ae.s', vae) -} - -function setPreferredValue( - preferred: string, - match: string, - widget: IBaseWidget | undefined -): void { - if (!widget) throw new TypeError('Widget not found') - - const { values } = widget.options - if (!Array.isArray(values)) return - - // Match against filename portion only - const mapped = values.map((x) => parseFilePath(x).filename) - const value = - mapped.find((x) => x === preferred) ?? - mapped.find((x) => x.includes?.(match)) - widget.value = value ?? preferred -} - -function getPosToRightOfNode(fromNode: LGraphNode) { - const nodes = app.canvas.graph?.nodes - if (!nodes) throw new TypeError('Could not get graph nodes') - - const pos = [ - fromNode.pos[0] + fromNode.size[0] + 100, - fromNode.pos[1] - ] satisfies Point - - while (nodes.find((x) => isPointTooClose(x.pos, pos))) { - pos[0] += 20 - pos[1] += 20 - } - - return pos -} - -function connectPreviousLatent(fromNode: LGraphNode, toEditNode: LGraphNode) { - const { canvas } = app - const { graph } = canvas - if (!graph) throw new TypeError('Graph is not initialized') - - const l = findNearestOutputOfType([fromNode], 'LATENT') - if (!l) { - const imageOutput = findNearestOutputOfType([fromNode], 'IMAGE') - if (!imageOutput) throw new TypeError('No image output found') - - const vaeEncode = LiteGraph.createNode('VAEEncode') - if (!vaeEncode) throw new TypeError('Failed to create node') - - const { node: imageNode, index: imageIndex } = imageOutput - graph.add(vaeEncode) - vaeEncode.pos = getPosToRightOfNode(fromNode) - vaeEncode.pos[1] -= 200 - - vaeEncode.connect(0, toEditNode, 0) - imageNode.connect(imageIndex, vaeEncode, 0) - return - } - - const { node, index } = l - - node.connect(index, toEditNode, 0) -} - -function getInputNodes(node: LGraphNode): LGraphNode[] { - return node.inputs - .map((x) => LLink.resolve(x.link, app.graph)?.outputNode) - .filter((x) => !!x) -} - -function getOutputOfType( - node: LGraphNode, - type: string -): { - output: INodeOutputSlot - index: number -} { - const index = node.outputs.findIndex((x) => x.type === type) - const output = node.outputs[index] - return { output, index } -} - -function findNearestOutputOfType( - nodes: Iterable, - type: string = 'LATENT', - depth: number = 0 -): { node: LGraphNode; index: number } | undefined { - for (const node of nodes) { - const { output, index } = getOutputOfType(node, type) - if (output) return { node, index } - } - - if (depth < 3) { - const closestNodes = new Set([...nodes].flatMap((x) => getInputNodes(x))) - const res = findNearestOutputOfType(closestNodes, type, depth + 1) - if (res) return res - } -} - -function isPointTooClose(a: Point, b: Point, precision: number = 5) { - return Math.abs(a[0] - b[0]) < precision && Math.abs(a[1] - b[1]) < precision -} From 5f349ed3cd409685d62e67df18982e063b49587d Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 21 Aug 2025 00:15:43 +0900 Subject: [PATCH 4/5] chore: storybook-doc added (#5122) --- .storybook/README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.storybook/README.md b/.storybook/README.md index f35753d27c..eff19a67db 100644 --- a/.storybook/README.md +++ b/.storybook/README.md @@ -93,6 +93,44 @@ export const WithVariant: Story = { ## Development Tips +## ComfyUI Storybook Guidelines + +### Scope – When to Create Stories +- **PrimeVue components**: + No need to create stories. Just refer to the official PrimeVue documentation. +- **Custom shared components (design system components)**: + Always create stories. These components are built in collaboration with designers, and Storybook serves as both documentation and a communication tool. +- **Container components (logic-heavy)**: + Do not create stories. Only the underlying pure UI components should be included in Storybook. + +### Maintenance Philosophy +- Stories are lightweight and generally stable. + Once created, they rarely need updates unless: + - The design changes + - New props (e.g. size, color variants) are introduced +- For existing usage patterns, simply copy real code examples into Storybook to create stories. + +### File Placement +- Keep `*.stories.ts` files at the **same level as the component** (similar to test files). +- This makes it easier to check usage examples without navigating to another directory. + +### Developer/Designer Workflow +- **UI vs Container**: Separate pure UI components from container components. + Only UI components should live in Storybook. +- **Communication Tool**: Storybook is not just about code quality—it enables designers and developers to see: + - Which props exist + - What cases are covered + - How variants (e.g. size, colors) look in isolation +- **Example**: + `PackActionButton.vue` wraps a PrimeVue button with additional logic. + → Only create a story for the base UI button, not for the wrapper. + +### Suggested Workflow +1. Use PrimeVue docs for standard components +2. Use Storybook for **shared/custom components** that define our design system +3. Keep story files alongside components +4. When in doubt, focus on components reused across the app or those that need to be showcased to designers + ### Best Practices 1. **Keep Stories Simple**: Each story should demonstrate one specific use case @@ -135,6 +173,7 @@ export const WithLongText: Story = { - **`main.ts`**: Core Storybook configuration and Vite integration - **`preview.ts`**: Global decorators, parameters, and Vue app setup - **`manager.ts`**: Storybook UI customization (if needed) +- **`preview-head.html`**: Injects custom HTML into the `` of every Storybook iframe (used for global styles, fonts, or fixes for iframe-specific issues) ## Chromatic Visual Testing @@ -170,4 +209,4 @@ This Storybook setup includes: - PrimeVue component library integration - Proper alias resolution for `@/` imports -For component-specific examples, see the NodePreview stories in `src/components/node/`. \ No newline at end of file +For component-specific examples, see the NodePreview stories in `src/components/node/`. From 240774842563b303cfe332a89e2677ee0081a8a9 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 21 Aug 2025 01:36:19 +0900 Subject: [PATCH 5/5] [feat] Add enhanced filter UI components with search and clear functionality (#5119) * [feat] Add enhanced filter UI components with search and clear functionality - Add SearchBox, clear all button, and item count to MultiSelect header - Add 'fit-content' size option to button types for flexible sizing - Update SingleSelect and ModelSelector components for consistency - Add localization strings for item selection and clear all functionality Co-Authored-By: Claude * Update locales [skip ci] --------- Co-authored-by: Claude Co-authored-by: github-actions --- src/components/input/MultiSelect.vue | 88 ++++++++++++++++++++----- src/components/input/SearchBox.vue | 21 ++++-- src/components/input/SingleSelect.vue | 2 +- src/components/widget/ModelSelector.vue | 4 ++ src/locales/ar/main.json | 3 + src/locales/en/main.json | 3 + src/locales/es/main.json | 3 + src/locales/fr/main.json | 3 + src/locales/ja/main.json | 3 + src/locales/ko/main.json | 3 + src/locales/ru/main.json | 3 + src/locales/zh-TW/main.json | 3 + src/locales/zh/main.json | 3 + src/types/buttonTypes.ts | 4 +- 14 files changed, 121 insertions(+), 25 deletions(-) diff --git a/src/components/input/MultiSelect.vue b/src/components/input/MultiSelect.vue index 2dd01dcb57..d1d02a4f16 100644 --- a/src/components/input/MultiSelect.vue +++ b/src/components/input/MultiSelect.vue @@ -9,6 +9,41 @@ :max-selected-labels="0" :pt="pt" > + + - +
() +import SearchBox from '@/components/input/SearchBox.vue' -const selectedItems = defineModel<{ name: string; value: string }[]>({ +import TextButton from '../button/TextButton.vue' + +type Option = { name: string; value: string } + +interface Props { + /** Input label shown on the trigger button */ + label?: string + /** Static options for the multiselect (when not using async search) */ + options: Option[] + /** Show search box in the panel header */ + hasSearchBox?: boolean + /** Show selected count text in the panel header */ + showSelectedCount?: boolean + /** Show "Clear all" action in the panel header */ + hasClearButton?: boolean + /** Placeholder for the search input */ + searchPlaceholder?: string +} +const { + label, + options, + hasSearchBox = false, + showSelectedCount = false, + hasClearButton = false, + searchPlaceholder = 'Search...' +} = defineProps() + +const selectedItems = defineModel({ required: true }) - +const searchQuery = defineModel('searchQuery') const selectedCount = computed(() => selectedItems.value.length) -/** - * Pure unstyled mode using only the PrimeVue PT API. - * All PrimeVue built-in checkboxes/headers are hidden via PT (no :deep hacks). - * Visual output matches the previous version exactly. - */ const pt = computed(() => ({ root: ({ props }: MultiSelectPassThroughMethodOptions) => ({ class: [ @@ -97,19 +151,19 @@ const pt = computed(() => ({ dropdown: { class: 'flex shrink-0 cursor-pointer items-center justify-center px-3' }, - header: { class: 'hidden' }, - + header: () => ({ + class: + hasSearchBox || showSelectedCount || hasClearButton ? 'block' : 'hidden' + }), // Overlay & list visuals unchanged overlay: - 'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100', + 'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100 dark-theme:border-zinc-700', list: { class: 'flex flex-col gap-1 p-0 list-none border-none text-xs' }, - // Option row hover tone identical option: 'flex gap-1 items-center p-2 hover:bg-neutral-100/50 dark-theme:hover:bg-zinc-700/50', - // Hide built-in checkboxes entirely via PT (no :deep) pcHeaderCheckbox: { root: { class: 'hidden' }, diff --git a/src/components/input/SearchBox.vue b/src/components/input/SearchBox.vue index 08075b18b1..462668a123 100644 --- a/src/components/input/SearchBox.vue +++ b/src/components/input/SearchBox.vue @@ -1,8 +1,6 @@