mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Partial execute to selected output nodes (#3818)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
85
browser_tests/assets/execution/partial_execution.json
Normal file
85
browser_tests/assets/execution/partial_execution.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"id": "1a95532f-c8aa-4c9d-a7f6-f928ba2d4862",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 3,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewAny",
|
||||
"pos": [946.2566528320312, 598.4373168945312],
|
||||
"size": [140, 76],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PreviewAny",
|
||||
"pos": [951.0236206054688, 421.3861083984375],
|
||||
"size": [140, 76],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PrimitiveString",
|
||||
"pos": [575.1760864257812, 504.5214538574219],
|
||||
"size": [270, 58],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [2, 3]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PrimitiveString"
|
||||
},
|
||||
"widgets_values": ["foo"]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[2, 3, 0, 1, 0, "*"],
|
||||
[3, 3, 0, 4, 0, "*"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"frontendVersion": "1.19.1",
|
||||
"ds": {
|
||||
"offset": [400, 400],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -18,3 +18,27 @@ test.describe('Execution', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Execute to selected output nodes', () => {
|
||||
test('Execute to selected output nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution/partial_execution')
|
||||
const input = await comfyPage.getNodeRefById(3)
|
||||
const output1 = await comfyPage.getNodeRefById(1)
|
||||
const output2 = await comfyPage.getNodeRefById(4)
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
|
||||
await output1.click('title')
|
||||
|
||||
await comfyPage.executeCommand('Comfy.QueueSelectedOutputNodes')
|
||||
// @note: Wait for the execution to finish. We might want to move to a more
|
||||
// reliable way to wait for the execution to finish. Workflow in this test
|
||||
// is simple enough that this is fine for now.
|
||||
await comfyPage.page.waitForTimeout(200)
|
||||
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
content: 'p-0 flex flex-row'
|
||||
}"
|
||||
>
|
||||
<ExecuteButton v-show="nodeSelected" />
|
||||
<ColorPickerButton v-show="nodeSelected || groupSelected" />
|
||||
<Button
|
||||
v-show="nodeSelected"
|
||||
@@ -74,6 +75,7 @@ import Panel from 'primevue/panel'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
|
||||
import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'
|
||||
import { useRefreshableSelection } from '@/composables/useRefreshableSelection'
|
||||
import { st, t } from '@/i18n'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
|
||||
71
src/components/graph/selectionToolbox/ExecuteButton.vue
Normal file
71
src/components/graph/selectionToolbox/ExecuteButton.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<Button
|
||||
v-tooltip.top="{
|
||||
value: isDisabled
|
||||
? t('selectionToolbox.executeButton.disabledTooltip')
|
||||
: t('selectionToolbox.executeButton.tooltip'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
:severity="isDisabled ? 'secondary' : 'success'"
|
||||
text
|
||||
:disabled="isDisabled"
|
||||
@mouseenter="() => handleMouseEnter()"
|
||||
@mouseleave="() => handleMouseLeave()"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i-lucide:play />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import Button from 'primevue/button'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { isLGraphNode } from '@/utils/litegraphUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
const canvasStore = useCanvasStore()
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
const canvas = canvasStore.getCanvas()
|
||||
const buttonHovered = ref(false)
|
||||
const selectedOutputNodes = computed(
|
||||
() =>
|
||||
canvasStore.selectedItems.filter(
|
||||
(item) => isLGraphNode(item) && item.constructor.nodeData.output_node
|
||||
) as LGraphNode[]
|
||||
)
|
||||
|
||||
const isDisabled = computed(() => selectedOutputNodes.value.length === 0)
|
||||
|
||||
function outputNodeStokeStyle(this: LGraphNode) {
|
||||
if (
|
||||
this.selected &&
|
||||
this.constructor.nodeData.output_node &&
|
||||
buttonHovered.value
|
||||
) {
|
||||
return { color: 'orange', lineWidth: 2, padding: 10 }
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
buttonHovered.value = true
|
||||
for (const node of selectedOutputNodes.value) {
|
||||
node.strokeStyles['outputNode'] = outputNodeStokeStyle
|
||||
}
|
||||
canvas.setDirty(true)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
buttonHovered.value = false
|
||||
canvas.setDirty(true)
|
||||
}
|
||||
|
||||
const handleClick = async () => {
|
||||
await commandStore.execute('Comfy.QueueSelectedOutputNodes')
|
||||
}
|
||||
</script>
|
||||
@@ -312,6 +312,28 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
await app.queuePrompt(-1, batchCount)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.QueueSelectedOutputNodes',
|
||||
icon: 'pi pi-play',
|
||||
label: 'Queue Selected Output Nodes',
|
||||
versionAdded: '1.19.6',
|
||||
function: async () => {
|
||||
const batchCount = useQueueSettingsStore().batchCount
|
||||
const queueNodeIds = getSelectedNodes()
|
||||
.filter((node) => node.constructor.nodeData.output_node)
|
||||
.map((node) => node.id)
|
||||
if (queueNodeIds.length === 0) {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.nothingToQueue'),
|
||||
detail: t('toastMessages.pleaseSelectOutputNodes'),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
await app.queuePrompt(0, batchCount, queueNodeIds)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ShowSettingsDialog',
|
||||
icon: 'pi pi-cog',
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Queue Prompt (Front)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Queue Selected Output Nodes"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Redo"
|
||||
},
|
||||
|
||||
@@ -803,6 +803,7 @@
|
||||
"Open": "Open",
|
||||
"Queue Prompt": "Queue Prompt",
|
||||
"Queue Prompt (Front)": "Queue Prompt (Front)",
|
||||
"Queue Selected Output Nodes": "Queue Selected Output Nodes",
|
||||
"Redo": "Redo",
|
||||
"Refresh Node Definitions": "Refresh Node Definitions",
|
||||
"Save": "Save",
|
||||
@@ -1197,6 +1198,8 @@
|
||||
"resizeNodeMatchOutput": "Resize Node to match output"
|
||||
},
|
||||
"toastMessages": {
|
||||
"nothingToQueue": "Nothing to queue",
|
||||
"pleaseSelectOutputNodes": "Please select output nodes",
|
||||
"no3dScene": "No 3D scene to apply texture",
|
||||
"failedToApplyTexture": "Failed to apply texture",
|
||||
"no3dSceneToExport": "No 3D scene to export",
|
||||
@@ -1343,5 +1346,11 @@
|
||||
"title": "Run!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"tooltip": "Execute to selected output nodes (Highlighted with orange border)",
|
||||
"disabledTooltip": "No output nodes selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Prompt de Cola (Frente)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Encolar nodos de salida seleccionados"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Rehacer"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
|
||||
"Queue Prompt": "Indicador de cola",
|
||||
"Queue Prompt (Front)": "Indicador de cola (Frente)",
|
||||
"Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
|
||||
"Quit": "Salir",
|
||||
"Redo": "Rehacer",
|
||||
"Refresh Node Definitions": "Actualizar definiciones de nodo",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "Tu dispositivo no es compatible"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "No hay nodos de salida seleccionados",
|
||||
"tooltip": "Ejecutar en los nodos de salida seleccionados (resaltados con borde naranja)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Has modificado las siguientes configuraciones del servidor. Reinicia para aplicar los cambios.",
|
||||
"restart": "Reiniciar",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "No hay plantillas para exportar",
|
||||
"nodeDefinitionsUpdated": "Definiciones de nodos actualizadas",
|
||||
"nothingToGroup": "Nada para agrupar",
|
||||
"nothingToQueue": "Nada para poner en cola",
|
||||
"pendingTasksDeleted": "Tareas pendientes eliminadas",
|
||||
"pleaseSelectNodesToGroup": "Por favor, seleccione los nodos (u otros grupos) para crear un grupo para",
|
||||
"pleaseSelectOutputNodes": "Por favor, selecciona los nodos de salida",
|
||||
"unableToGetModelFilePath": "No se puede obtener la ruta del archivo del modelo",
|
||||
"unauthorizedDomain": "Tu dominio {domain} no está autorizado para usar este servicio. Por favor, contacta a {email} para agregar tu dominio a la lista blanca.",
|
||||
"updateRequested": "Actualización solicitada",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Invite de file d'attente (avant)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Mettre en file d’attente les nœuds de sortie sélectionnés"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Refaire"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "Flux de travail ouvert précédent",
|
||||
"Queue Prompt": "Invite de file d'attente",
|
||||
"Queue Prompt (Front)": "Invite de file d'attente (Front)",
|
||||
"Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés",
|
||||
"Quit": "Quitter",
|
||||
"Redo": "Refaire",
|
||||
"Refresh Node Definitions": "Actualiser les définitions de nœud",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "Votre appareil n'est pas pris en charge"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "Aucun nœud de sortie sélectionné",
|
||||
"tooltip": "Exécuter vers les nœuds de sortie sélectionnés (surlignés avec une bordure orange)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Vous avez modifié les configurations suivantes du serveur. Redémarrez pour appliquer les modifications.",
|
||||
"restart": "Redémarrer",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "Aucun modèle à exporter",
|
||||
"nodeDefinitionsUpdated": "Définitions de nœuds mises à jour",
|
||||
"nothingToGroup": "Rien à regrouper",
|
||||
"nothingToQueue": "Rien à ajouter à la file d’attente",
|
||||
"pendingTasksDeleted": "Tâches en attente supprimées",
|
||||
"pleaseSelectNodesToGroup": "Veuillez sélectionner les nœuds (ou autres groupes) pour créer un groupe pour",
|
||||
"pleaseSelectOutputNodes": "Veuillez sélectionner les nœuds de sortie",
|
||||
"unableToGetModelFilePath": "Impossible d'obtenir le chemin du fichier modèle",
|
||||
"unauthorizedDomain": "Votre domaine {domain} n'est pas autorisé à utiliser ce service. Veuillez contacter {email} pour ajouter votre domaine à la liste blanche.",
|
||||
"updateRequested": "Mise à jour demandée",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "キュープロンプト(フロント)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "選択した出力ノードをキューに追加"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "やり直す"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "前に開いたワークフロー",
|
||||
"Queue Prompt": "キューのプロンプト",
|
||||
"Queue Prompt (Front)": "キューのプロンプト (前面)",
|
||||
"Queue Selected Output Nodes": "選択した出力ノードをキューに追加",
|
||||
"Quit": "終了",
|
||||
"Redo": "やり直す",
|
||||
"Refresh Node Definitions": "ノード定義を更新",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "お使いのデバイスはサポートされていません"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "出力ノードが選択されていません",
|
||||
"tooltip": "選択した出力ノードに実行(オレンジ色の枠でハイライト表示)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "以下のサーバー設定を変更しました。変更を適用するには再起動してください。",
|
||||
"restart": "再起動",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "エクスポートするテンプレートがありません",
|
||||
"nodeDefinitionsUpdated": "ノード定義が更新されました",
|
||||
"nothingToGroup": "グループ化するものがありません",
|
||||
"nothingToQueue": "キューに追加する項目がありません",
|
||||
"pendingTasksDeleted": "保留中のタスクが削除されました",
|
||||
"pleaseSelectNodesToGroup": "グループを作成するためのノード(または他のグループ)を選択してください",
|
||||
"pleaseSelectOutputNodes": "出力ノードを選択してください",
|
||||
"unableToGetModelFilePath": "モデルファイルのパスを取得できません",
|
||||
"unauthorizedDomain": "あなたのドメイン {domain} はこのサービスを利用する権限がありません。ご利用のドメインをホワイトリストに追加するには、{email} までご連絡ください。",
|
||||
"updateRequested": "更新が要求されました",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "실행 큐 맨 앞에 프롬프트 추가"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "선택한 출력 노드 대기열에 추가"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "다시 실행"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "이전 열린 워크플로",
|
||||
"Queue Prompt": "실행 큐에 프롬프트 추가",
|
||||
"Queue Prompt (Front)": "실행 큐 맨 앞에 프롬프트 추가",
|
||||
"Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가",
|
||||
"Quit": "종료",
|
||||
"Redo": "다시 실행",
|
||||
"Refresh Node Definitions": "노드 정의 새로 고침",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "이 장치는 지원되지 않습니다."
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "선택된 출력 노드가 없습니다",
|
||||
"tooltip": "선택한 출력 노드에 실행 (주황색 테두리로 강조 표시됨)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "다음 서버 구성을 수정했습니다. 변경 사항을 적용하려면 다시 시작하세오.",
|
||||
"restart": "다시 시작",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "내보낼 템플릿이 없습니다",
|
||||
"nodeDefinitionsUpdated": "노드 정의가 업데이트되었습니다",
|
||||
"nothingToGroup": "그룹화할 항목이 없습니다",
|
||||
"nothingToQueue": "대기열에 추가할 항목이 없습니다",
|
||||
"pendingTasksDeleted": "보류 중인 작업이 삭제되었습니다",
|
||||
"pleaseSelectNodesToGroup": "그룹을 만들기 위해 노드(또는 다른 그룹)를 선택해 주세요",
|
||||
"pleaseSelectOutputNodes": "출력 노드를 선택해 주세요",
|
||||
"unableToGetModelFilePath": "모델 파일 경로를 가져올 수 없습니다",
|
||||
"unauthorizedDomain": "귀하의 도메인 {domain}은(는) 이 서비스를 사용할 수 있는 권한이 없습니다. 도메인을 허용 목록에 추가하려면 {email}로 문의해 주세요.",
|
||||
"updateRequested": "업데이트 요청됨",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Очередь запросов (передняя)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Добавить выбранные выходные узлы в очередь"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Повторить"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
|
||||
"Queue Prompt": "Запрос в очереди",
|
||||
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
|
||||
"Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь",
|
||||
"Quit": "Выйти",
|
||||
"Redo": "Повторить",
|
||||
"Refresh Node Definitions": "Обновить определения нод",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "Ваше устройство не поддерживается"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "Выходные узлы не выбраны",
|
||||
"tooltip": "Выполнить для выбранных выходных узлов (выделены оранжевой рамкой)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Вы изменили следующие конфигурации сервера. Перезапустите, чтобы применить изменения.",
|
||||
"restart": "Перезапустить",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "Нет шаблонов для экспорта",
|
||||
"nodeDefinitionsUpdated": "Определения узлов обновлены",
|
||||
"nothingToGroup": "Нечего группировать",
|
||||
"nothingToQueue": "Нет заданий в очереди",
|
||||
"pendingTasksDeleted": "Ожидающие задачи удалены",
|
||||
"pleaseSelectNodesToGroup": "Пожалуйста, выберите узлы (или другие группы) для создания группы",
|
||||
"pleaseSelectOutputNodes": "Пожалуйста, выберите выходные узлы",
|
||||
"unableToGetModelFilePath": "Не удалось получить путь к файлу модели",
|
||||
"unauthorizedDomain": "Ваш домен {domain} не авторизован для использования этого сервиса. Пожалуйста, свяжитесь с {email}, чтобы добавить ваш домен в белый список.",
|
||||
"updateRequested": "Запрошено обновление",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "执行提示词(前端)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "队列所选输出节点"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "重做"
|
||||
},
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
"Previous Opened Workflow": "上一个打开的工作流",
|
||||
"Queue Prompt": "执行提示词",
|
||||
"Queue Prompt (Front)": "执行提示词 (优先执行)",
|
||||
"Queue Selected Output Nodes": "将所选输出节点加入队列",
|
||||
"Quit": "退出",
|
||||
"Redo": "重做",
|
||||
"Refresh Node Definitions": "刷新节点定义",
|
||||
@@ -801,6 +802,12 @@
|
||||
},
|
||||
"title": "您的设备不受支持"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "未选择输出节点",
|
||||
"tooltip": "执行到选定的输出节点(用橙色边框高亮显示)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "您已修改以下服务器配置。重启以应用更改。",
|
||||
"restart": "重启",
|
||||
@@ -1297,8 +1304,10 @@
|
||||
"noTemplatesToExport": "没有模板可以导出",
|
||||
"nodeDefinitionsUpdated": "节点定义已更新",
|
||||
"nothingToGroup": "没有可分组的内容",
|
||||
"nothingToQueue": "没有可加入队列的内容",
|
||||
"pendingTasksDeleted": "待处理任务已删除",
|
||||
"pleaseSelectNodesToGroup": "请选取节点(或其他组)以创建分组",
|
||||
"pleaseSelectOutputNodes": "请选择输出节点",
|
||||
"unableToGetModelFilePath": "无法获取模型文件路径",
|
||||
"unauthorizedDomain": "您的域名 {domain} 未被授权使用此服务。请联系 {email} 将您的域名添加到白名单。",
|
||||
"updateRequested": "已请求更新",
|
||||
|
||||
@@ -107,7 +107,11 @@ export class ComfyApp {
|
||||
/**
|
||||
* List of entries to queue
|
||||
*/
|
||||
#queueItems: { number: number; batchCount: number }[] = []
|
||||
#queueItems: {
|
||||
number: number
|
||||
batchCount: number
|
||||
queueNodeIds?: NodeId[]
|
||||
}[] = []
|
||||
/**
|
||||
* If the queue is currently being processed
|
||||
*/
|
||||
@@ -1144,14 +1148,22 @@ export class ComfyApp {
|
||||
})
|
||||
}
|
||||
|
||||
async graphToPrompt(graph = this.graph) {
|
||||
async graphToPrompt(
|
||||
graph = this.graph,
|
||||
options: { queueNodeIds?: NodeId[] } = {}
|
||||
) {
|
||||
return graphToPrompt(graph, {
|
||||
sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave')
|
||||
sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave'),
|
||||
queueNodeIds: options.queueNodeIds
|
||||
})
|
||||
}
|
||||
|
||||
async queuePrompt(number: number, batchCount: number = 1): Promise<boolean> {
|
||||
this.#queueItems.push({ number, batchCount })
|
||||
async queuePrompt(
|
||||
number: number,
|
||||
batchCount: number = 1,
|
||||
queueNodeIds?: NodeId[]
|
||||
): Promise<boolean> {
|
||||
this.#queueItems.push({ number, batchCount, queueNodeIds })
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
if (this.#processingQueue) {
|
||||
@@ -1167,14 +1179,14 @@ export class ComfyApp {
|
||||
|
||||
try {
|
||||
while (this.#queueItems.length) {
|
||||
const { number, batchCount } = this.#queueItems.pop()!
|
||||
const { number, batchCount, queueNodeIds } = this.#queueItems.pop()!
|
||||
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
// Allow widgets to run callbacks before a prompt has been queued
|
||||
// e.g. random seed before every gen
|
||||
executeWidgetsCallback(this.graph.nodes, 'beforeQueued')
|
||||
|
||||
const p = await this.graphToPrompt()
|
||||
const p = await this.graphToPrompt(this.graph, { queueNodeIds })
|
||||
try {
|
||||
api.authToken = comfyOrgAuthToken
|
||||
const res = await api.queuePrompt(number, p)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LGraph } from '@comfyorg/litegraph'
|
||||
import type { LGraph, NodeId } from '@comfyorg/litegraph'
|
||||
import { LGraphEventMode } from '@comfyorg/litegraph'
|
||||
|
||||
import type {
|
||||
@@ -8,16 +8,46 @@ import type {
|
||||
|
||||
import { compressWidgetInputSlots } from './litegraphUtil'
|
||||
|
||||
/**
|
||||
* Recursively target node's parent nodes to the new output.
|
||||
* @param nodeId The node id to add.
|
||||
* @param oldOutput The old output.
|
||||
* @param newOutput The new output.
|
||||
* @returns The new output.
|
||||
*/
|
||||
function recursiveAddNodes(
|
||||
nodeId: NodeId,
|
||||
oldOutput: ComfyApiWorkflow,
|
||||
newOutput: ComfyApiWorkflow
|
||||
) {
|
||||
const currentId = String(nodeId)
|
||||
const currentNode = oldOutput[currentId]!
|
||||
if (newOutput[currentId] == null) {
|
||||
newOutput[currentId] = currentNode
|
||||
for (const inputValue of Object.values(currentNode.inputs || [])) {
|
||||
if (Array.isArray(inputValue)) {
|
||||
recursiveAddNodes(inputValue[0], oldOutput, newOutput)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current graph workflow for sending to the API.
|
||||
* Note: Node widgets are updated before serialization to prepare queueing.
|
||||
* @note Node widgets are updated before serialization to prepare queueing.
|
||||
*
|
||||
* @param graph The graph to convert.
|
||||
* @param options The options for the conversion.
|
||||
* - `sortNodes`: Whether to sort the nodes by execution order.
|
||||
* - `queueNodeIds`: The output nodes to execute. Execute all output nodes if not provided.
|
||||
* @returns The workflow and node links
|
||||
*/
|
||||
export const graphToPrompt = async (
|
||||
graph: LGraph,
|
||||
options: { sortNodes?: boolean } = {}
|
||||
options: { sortNodes?: boolean; queueNodeIds?: NodeId[] } = {}
|
||||
): Promise<{ workflow: ComfyWorkflowJSON; output: ComfyApiWorkflow }> => {
|
||||
const { sortNodes = false } = options
|
||||
const { sortNodes = false, queueNodeIds } = options
|
||||
|
||||
for (const node of graph.computeExecutionOrder(false)) {
|
||||
const innerNodes = node.getInnerNodes ? node.getInnerNodes() : [node]
|
||||
@@ -44,7 +74,7 @@ export const graphToPrompt = async (
|
||||
workflow.extra ??= {}
|
||||
workflow.extra.frontendVersion = __COMFYUI_FRONTEND_VERSION__
|
||||
|
||||
const output: ComfyApiWorkflow = {}
|
||||
let output: ComfyApiWorkflow = {}
|
||||
// Process nodes in order of execution
|
||||
for (const outerNode of graph.computeExecutionOrder(false)) {
|
||||
const skipNode =
|
||||
@@ -163,6 +193,15 @@ export const graphToPrompt = async (
|
||||
}
|
||||
}
|
||||
|
||||
// Partial execution
|
||||
if (queueNodeIds?.length) {
|
||||
const newOutput = {}
|
||||
for (const queueNodeId of queueNodeIds) {
|
||||
recursiveAddNodes(queueNodeId, output, newOutput)
|
||||
}
|
||||
output = newOutput
|
||||
}
|
||||
|
||||
// @ts-expect-error Convert ISerializedGraph to ComfyWorkflowJSON
|
||||
return { workflow: workflow as ComfyWorkflowJSON, output }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user