Expose dialogService to extensionManager (#2113)

This commit is contained in:
Chenlei Hu
2024-12-31 17:01:37 -05:00
committed by GitHub
parent 0b3c0cc0c9
commit 174a9a114a
11 changed files with 66 additions and 18 deletions

View File

@@ -229,6 +229,26 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Developer APIs ### Developer APIs
<details>
<summary>v1.6.13: Prompt dialog</summary>
`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)
</details>
<details> <details>
<summary>v1.3.34: Register about panel badges</summary> <summary>v1.3.34: Register about panel badges</summary>

View File

@@ -158,4 +158,24 @@ test.describe('Topbar commands', () => {
expect(await badge.textContent()).toContain('Test Badge') 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!'
)
})
})
}) })

View File

@@ -530,6 +530,13 @@ export class ComfyPage {
return this.page.locator('.p-dialog-content input[type="text"]') 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() { async disconnectEdge() {
await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace) await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace)
} }
@@ -797,9 +804,7 @@ export class ComfyPage {
await this.canvas.press('Control+a') await this.canvas.press('Control+a')
const node = await this.getFirstNodeRef() const node = await this.getFirstNodeRef()
await node!.clickContextMenuOption('Convert to Group Node') await node!.clickContextMenuOption('Convert to Group Node')
await this.promptDialogInput.fill(groupNodeName) await this.fillPromptDialog(groupNodeName)
await this.page.keyboard.press('Enter')
await this.promptDialogInput.waitFor({ state: 'hidden' })
await this.nextFrame() await this.nextFrame()
} }

View File

@@ -235,9 +235,7 @@ export class NodeReference {
} }
async convertToGroupNode(groupNodeName: string = 'GroupNode') { async convertToGroupNode(groupNodeName: string = 'GroupNode') {
await this.clickContextMenuOption('Convert to Group Node') await this.clickContextMenuOption('Convert to Group Node')
await this.comfyPage.promptDialogInput.fill(groupNodeName) await this.comfyPage.fillPromptDialog(groupNodeName)
await this.comfyPage.page.keyboard.press('Enter')
await this.comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
await this.comfyPage.nextFrame() await this.comfyPage.nextFrame()
const nodes = await this.comfyPage.getNodeRefsByType( const nodes = await this.comfyPage.getNodeRefsByType(
`workflow>${groupNodeName}` `workflow>${groupNodeName}`

View File

@@ -115,7 +115,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
label: 'Reinstall', label: 'Reinstall',
icon: 'pi pi-refresh', icon: 'pi pi-refresh',
async function() { async function() {
const proceed = await useDialogService().showConfirmationDialog({ const proceed = await useDialogService().confirm({
message: t('desktopMenu.confirmReinstall'), message: t('desktopMenu.confirmReinstall'),
title: t('desktopMenu.reinstall'), title: t('desktopMenu.reinstall'),
type: 'reinstall' type: 'reinstall'

View File

@@ -79,7 +79,7 @@ class GroupNodeBuilder {
} }
async getName() { async getName() {
const name = await useDialogService().showPromptDialog({ const name = await useDialogService().prompt({
title: t('groupNode.create'), title: t('groupNode.create'),
message: t('groupNode.enterName'), message: t('groupNode.enterName'),
defaultValue: '' defaultValue: ''

View File

@@ -353,7 +353,7 @@ app.registerExtension({
content: `Save Selected as Template`, content: `Save Selected as Template`,
disabled: !Object.keys(app.canvas.selected_nodes || {}).length, disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
callback: async () => { callback: async () => {
const name = await useDialogService().showPromptDialog({ const name = await useDialogService().prompt({
title: t('nodeTemplates.saveAsTemplate'), title: t('nodeTemplates.saveAsTemplate'),
message: t('nodeTemplates.enterName'), message: t('nodeTemplates.enterName'),
defaultValue: '' defaultValue: ''

View File

@@ -84,7 +84,7 @@ export const useDialogService = () => {
}) })
} }
async function showPromptDialog({ async function prompt({
title, title,
message, message,
defaultValue = '' defaultValue = ''
@@ -119,7 +119,7 @@ export const useDialogService = () => {
* `false` if denied (e.g. no in yes/no/cancel), or * `false` if denied (e.g. no in yes/no/cancel), or
* `null` if the dialog is cancelled or closed * `null` if the dialog is cancelled or closed
*/ */
async function showConfirmationDialog({ async function confirm({
title, title,
type, type,
message, message,
@@ -161,7 +161,7 @@ export const useDialogService = () => {
showAboutDialog, showAboutDialog,
showExecutionErrorDialog, showExecutionErrorDialog,
showTemplateWorkflowsDialog, showTemplateWorkflowsDialog,
showPromptDialog, prompt,
showConfirmationDialog confirm
} }
} }

View File

@@ -22,7 +22,7 @@ export const useWorkflowService = () => {
async function getFilename(defaultName: string): Promise<string | null> { async function getFilename(defaultName: string): Promise<string | null> {
if (settingStore.get('Comfy.PromptFilename')) { if (settingStore.get('Comfy.PromptFilename')) {
let filename = await dialogService.showPromptDialog({ let filename = await dialogService.prompt({
title: t('workflowService.exportWorkflow'), title: t('workflowService.exportWorkflow'),
message: t('workflowService.enterFilename') + ':', message: t('workflowService.enterFilename') + ':',
defaultValue: defaultName defaultValue: defaultName
@@ -60,7 +60,7 @@ export const useWorkflowService = () => {
* @param workflow The workflow to save * @param workflow The workflow to save
*/ */
const saveWorkflowAs = async (workflow: ComfyWorkflow) => { const saveWorkflowAs = async (workflow: ComfyWorkflow) => {
const newFilename = await dialogService.showPromptDialog({ const newFilename = await dialogService.prompt({
title: t('workflowService.saveWorkflow'), title: t('workflowService.saveWorkflow'),
message: t('workflowService.enterFilename') + ':', message: t('workflowService.enterFilename') + ':',
defaultValue: workflow.filename defaultValue: workflow.filename
@@ -72,7 +72,7 @@ export const useWorkflowService = () => {
const existingWorkflow = workflowStore.getWorkflowByPath(newPath) const existingWorkflow = workflowStore.getWorkflowByPath(newPath)
if (existingWorkflow && !existingWorkflow.isTemporary) { if (existingWorkflow && !existingWorkflow.isTemporary) {
const res = await dialogService.showConfirmationDialog({ const res = await dialogService.confirm({
title: t('sideToolbar.workflowTab.confirmOverwriteTitle'), title: t('sideToolbar.workflowTab.confirmOverwriteTitle'),
type: 'overwrite', type: 'overwrite',
message: t('sideToolbar.workflowTab.confirmOverwrite'), message: t('sideToolbar.workflowTab.confirmOverwrite'),
@@ -181,7 +181,7 @@ export const useWorkflowService = () => {
} }
if (workflow.isModified && options.warnIfUnsaved) { if (workflow.isModified && options.warnIfUnsaved) {
const confirmed = await dialogService.showConfirmationDialog({ const confirmed = await dialogService.confirm({
title: t('sideToolbar.workflowTab.dirtyCloseTitle'), title: t('sideToolbar.workflowTab.dirtyCloseTitle'),
type: 'dirtyClose', type: 'dirtyClose',
message: t('sideToolbar.workflowTab.dirtyClose'), message: t('sideToolbar.workflowTab.dirtyClose'),
@@ -225,7 +225,7 @@ export const useWorkflowService = () => {
let confirmed: boolean | null = bypassConfirm || silent let confirmed: boolean | null = bypassConfirm || silent
if (!confirmed) { if (!confirmed) {
confirmed = await dialogService.showConfirmationDialog({ confirmed = await dialogService.confirm({
title: t('sideToolbar.workflowTab.confirmDeleteTitle'), title: t('sideToolbar.workflowTab.confirmDeleteTitle'),
type: 'delete', type: 'delete',
message: t('sideToolbar.workflowTab.confirmDelete'), message: t('sideToolbar.workflowTab.confirmDelete'),

View File

@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useColorPaletteService } from '@/services/colorPaletteService' import { useColorPaletteService } from '@/services/colorPaletteService'
import { useDialogService } from '@/services/dialogService'
import type { SidebarTabExtension, ToastManager } from '@/types/extensionTypes' import type { SidebarTabExtension, ToastManager } from '@/types/extensionTypes'
import { useCommandStore } from './commandStore' import { useCommandStore } from './commandStore'
@@ -34,6 +35,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
})) }))
const workflow = computed(() => useWorkflowStore()) const workflow = computed(() => useWorkflowStore())
const colorPalette = useColorPaletteService() const colorPalette = useColorPaletteService()
const dialog = useDialogService()
/** /**
* Registers a sidebar tab. * Registers a sidebar tab.
@@ -76,6 +78,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
setting, setting,
workflow, workflow,
colorPalette, colorPalette,
dialog,
registerSidebarTab, registerSidebarTab,
unregisterSidebarTab, unregisterSidebarTab,

View File

@@ -1,5 +1,6 @@
import { Component } from 'vue' import { Component } from 'vue'
import type { useDialogService } from '@/services/dialogService'
import type { ComfyCommand } from '@/stores/commandStore' import type { ComfyCommand } from '@/stores/commandStore'
export interface BaseSidebarTabExtension { export interface BaseSidebarTabExtension {
@@ -102,6 +103,7 @@ export interface ExtensionManager {
getSidebarTabs(): SidebarTabExtension[] getSidebarTabs(): SidebarTabExtension[]
toast: ToastManager toast: ToastManager
dialog: ReturnType<typeof useDialogService>
command: CommandManager command: CommandManager
setting: { setting: {
get: (id: string) => any get: (id: string) => any