diff --git a/README.md b/README.md index 77ad8e0c8..62775372f 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,26 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d ### Developer APIs +
+ v1.6.13: Prompt dialog + +`window.prompt` is not available in ComfyUI desktop's electron environment. Please use the following API to show a prompt dialog. + +```js +window['app'].extensionManager.dialog + .prompt({ + title: 'Test Prompt', + message: 'Test Prompt Message' + }) + .then((value: string) => { + // Do something with the value user entered + }) +``` + +![image](https://github.com/user-attachments/assets/c73f74d0-9bb4-4555-8d56-83f1be4a1d7e) + +
+
v1.3.34: Register about panel badges diff --git a/browser_tests/extensionAPI.spec.ts b/browser_tests/extensionAPI.spec.ts index 9e95e31df..6615b4fb6 100644 --- a/browser_tests/extensionAPI.spec.ts +++ b/browser_tests/extensionAPI.spec.ts @@ -158,4 +158,24 @@ test.describe('Topbar commands', () => { expect(await badge.textContent()).toContain('Test Badge') }) }) + + test.describe('Dialog', () => { + test('Should allow showing a prompt dialog', async ({ comfyPage }) => { + await comfyPage.page.evaluate(() => { + window['app'].extensionManager.dialog + .prompt({ + title: 'Test Prompt', + message: 'Test Prompt Message' + }) + .then((value: string) => { + window['value'] = value + }) + }) + + await comfyPage.fillPromptDialog('Hello, world!') + expect(await comfyPage.page.evaluate(() => window['value'])).toBe( + 'Hello, world!' + ) + }) + }) }) diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index 545c26412..ea83f3042 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -530,6 +530,13 @@ export class ComfyPage { return this.page.locator('.p-dialog-content input[type="text"]') } + async fillPromptDialog(value: string) { + await this.promptDialogInput.fill(value) + await this.page.keyboard.press('Enter') + await this.promptDialogInput.waitFor({ state: 'hidden' }) + await this.nextFrame() + } + async disconnectEdge() { await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace) } @@ -797,9 +804,7 @@ export class ComfyPage { await this.canvas.press('Control+a') const node = await this.getFirstNodeRef() await node!.clickContextMenuOption('Convert to Group Node') - await this.promptDialogInput.fill(groupNodeName) - await this.page.keyboard.press('Enter') - await this.promptDialogInput.waitFor({ state: 'hidden' }) + await this.fillPromptDialog(groupNodeName) await this.nextFrame() } diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 6e6011b64..c7edab18b 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -235,9 +235,7 @@ export class NodeReference { } async convertToGroupNode(groupNodeName: string = 'GroupNode') { await this.clickContextMenuOption('Convert to Group Node') - await this.comfyPage.promptDialogInput.fill(groupNodeName) - await this.comfyPage.page.keyboard.press('Enter') - await this.comfyPage.promptDialogInput.waitFor({ state: 'hidden' }) + await this.comfyPage.fillPromptDialog(groupNodeName) await this.comfyPage.nextFrame() const nodes = await this.comfyPage.getNodeRefsByType( `workflow>${groupNodeName}` diff --git a/src/extensions/core/electronAdapter.ts b/src/extensions/core/electronAdapter.ts index 6f9581e2e..9f0a4f091 100644 --- a/src/extensions/core/electronAdapter.ts +++ b/src/extensions/core/electronAdapter.ts @@ -115,7 +115,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil' label: 'Reinstall', icon: 'pi pi-refresh', async function() { - const proceed = await useDialogService().showConfirmationDialog({ + const proceed = await useDialogService().confirm({ message: t('desktopMenu.confirmReinstall'), title: t('desktopMenu.reinstall'), type: 'reinstall' diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index bf16fbd8a..7fc4ed49f 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -79,7 +79,7 @@ class GroupNodeBuilder { } async getName() { - const name = await useDialogService().showPromptDialog({ + const name = await useDialogService().prompt({ title: t('groupNode.create'), message: t('groupNode.enterName'), defaultValue: '' diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index af8857942..a635bf9ca 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -353,7 +353,7 @@ app.registerExtension({ content: `Save Selected as Template`, disabled: !Object.keys(app.canvas.selected_nodes || {}).length, callback: async () => { - const name = await useDialogService().showPromptDialog({ + const name = await useDialogService().prompt({ title: t('nodeTemplates.saveAsTemplate'), message: t('nodeTemplates.enterName'), defaultValue: '' diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index ccf17b9a4..3fb8c6998 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -84,7 +84,7 @@ export const useDialogService = () => { }) } - async function showPromptDialog({ + async function prompt({ title, message, defaultValue = '' @@ -119,7 +119,7 @@ export const useDialogService = () => { * `false` if denied (e.g. no in yes/no/cancel), or * `null` if the dialog is cancelled or closed */ - async function showConfirmationDialog({ + async function confirm({ title, type, message, @@ -161,7 +161,7 @@ export const useDialogService = () => { showAboutDialog, showExecutionErrorDialog, showTemplateWorkflowsDialog, - showPromptDialog, - showConfirmationDialog + prompt, + confirm } } diff --git a/src/services/workflowService.ts b/src/services/workflowService.ts index c174c16c6..5f373e07a 100644 --- a/src/services/workflowService.ts +++ b/src/services/workflowService.ts @@ -22,7 +22,7 @@ export const useWorkflowService = () => { async function getFilename(defaultName: string): Promise { if (settingStore.get('Comfy.PromptFilename')) { - let filename = await dialogService.showPromptDialog({ + let filename = await dialogService.prompt({ title: t('workflowService.exportWorkflow'), message: t('workflowService.enterFilename') + ':', defaultValue: defaultName @@ -60,7 +60,7 @@ export const useWorkflowService = () => { * @param workflow The workflow to save */ const saveWorkflowAs = async (workflow: ComfyWorkflow) => { - const newFilename = await dialogService.showPromptDialog({ + const newFilename = await dialogService.prompt({ title: t('workflowService.saveWorkflow'), message: t('workflowService.enterFilename') + ':', defaultValue: workflow.filename @@ -72,7 +72,7 @@ export const useWorkflowService = () => { const existingWorkflow = workflowStore.getWorkflowByPath(newPath) if (existingWorkflow && !existingWorkflow.isTemporary) { - const res = await dialogService.showConfirmationDialog({ + const res = await dialogService.confirm({ title: t('sideToolbar.workflowTab.confirmOverwriteTitle'), type: 'overwrite', message: t('sideToolbar.workflowTab.confirmOverwrite'), @@ -181,7 +181,7 @@ export const useWorkflowService = () => { } if (workflow.isModified && options.warnIfUnsaved) { - const confirmed = await dialogService.showConfirmationDialog({ + const confirmed = await dialogService.confirm({ title: t('sideToolbar.workflowTab.dirtyCloseTitle'), type: 'dirtyClose', message: t('sideToolbar.workflowTab.dirtyClose'), @@ -225,7 +225,7 @@ export const useWorkflowService = () => { let confirmed: boolean | null = bypassConfirm || silent if (!confirmed) { - confirmed = await dialogService.showConfirmationDialog({ + confirmed = await dialogService.confirm({ title: t('sideToolbar.workflowTab.confirmDeleteTitle'), type: 'delete', message: t('sideToolbar.workflowTab.confirmDelete'), diff --git a/src/stores/workspaceStore.ts b/src/stores/workspaceStore.ts index d139a7eb3..71ff6c454 100644 --- a/src/stores/workspaceStore.ts +++ b/src/stores/workspaceStore.ts @@ -2,6 +2,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { useColorPaletteService } from '@/services/colorPaletteService' +import { useDialogService } from '@/services/dialogService' import type { SidebarTabExtension, ToastManager } from '@/types/extensionTypes' import { useCommandStore } from './commandStore' @@ -34,6 +35,7 @@ export const useWorkspaceStore = defineStore('workspace', () => { })) const workflow = computed(() => useWorkflowStore()) const colorPalette = useColorPaletteService() + const dialog = useDialogService() /** * Registers a sidebar tab. @@ -76,6 +78,7 @@ export const useWorkspaceStore = defineStore('workspace', () => { setting, workflow, colorPalette, + dialog, registerSidebarTab, unregisterSidebarTab, diff --git a/src/types/extensionTypes.ts b/src/types/extensionTypes.ts index 8ffde93d4..88e82c0e1 100644 --- a/src/types/extensionTypes.ts +++ b/src/types/extensionTypes.ts @@ -1,5 +1,6 @@ import { Component } from 'vue' +import type { useDialogService } from '@/services/dialogService' import type { ComfyCommand } from '@/stores/commandStore' export interface BaseSidebarTabExtension { @@ -102,6 +103,7 @@ export interface ExtensionManager { getSidebarTabs(): SidebarTabExtension[] toast: ToastManager + dialog: ReturnType command: CommandManager setting: { get: (id: string) => any