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