Merge branch 'main' into manager/menu-items-migration

This commit is contained in:
Jin Yi
2025-07-30 19:56:49 +09:00
committed by GitHub
182 changed files with 15831 additions and 2331 deletions

View File

@@ -0,0 +1,84 @@
import { register } from 'extendable-media-recorder'
import { connect } from 'extendable-media-recorder-wav-encoder'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'
export interface AudioRecordingError {
type: 'permission' | 'not_supported' | 'encoder' | 'recording' | 'unknown'
message: string
originalError?: unknown
}
let isEncoderRegistered: boolean = false
export const useAudioService = () => {
const handleError = (
type: AudioRecordingError['type'],
message: string,
originalError?: unknown
) => {
console.error(`Audio Service Error (${type}):`, message, originalError)
}
const stopAllTracks = (currentStream: MediaStream | null) => {
if (currentStream) {
currentStream.getTracks().forEach((track) => {
track.stop()
})
currentStream = null
}
}
const registerWavEncoder = async (): Promise<void> => {
if (isEncoderRegistered) {
return
}
try {
await register(await connect())
isEncoderRegistered = true
} catch (err) {
if (
err instanceof Error &&
err.message.includes('already an encoder stored')
) {
isEncoderRegistered = true
} else {
handleError('encoder', 'Failed to register WAV encoder', err)
}
}
}
const convertBlobToFileAndSubmit = async (blob: Blob): Promise<string> => {
const name = `recording-${Date.now()}.wav`
const file = new File([blob], name, { type: blob.type || 'audio/wav' })
const body = new FormData()
body.append('image', file)
body.append('subfolder', 'audio')
body.append('type', 'temp')
const resp = await api.fetchApi('/upload/image', {
method: 'POST',
body
})
if (resp.status !== 200) {
const err = `Error uploading temp file: ${resp.status} - ${resp.statusText}`
useToastStore().addAlert(err)
throw new Error(err)
}
const tempAudio = await resp.json()
return `audio/${tempAudio.name} [temp]`
}
return {
// Methods
convertBlobToFileAndSubmit,
registerWavEncoder,
stopAllTracks
}
}

View File

@@ -272,7 +272,7 @@ export const useDialogService = () => {
onSuccess: () => resolve(true)
},
dialogComponentProps: {
closable: false,
closable: true,
onClose: () => resolve(false)
}
})

View File

@@ -32,7 +32,10 @@ import type {
} from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
import { ComfyApp, app } from '@/scripts/app'
import { isComponentWidget, isDOMWidget } from '@/scripts/domWidget'
import { $el } from '@/scripts/ui'
import { useDomWidgetStore } from '@/stores/domWidgetStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useCanvasStore } from '@/stores/graphStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
@@ -87,6 +90,37 @@ export const useLitegraphService = () => {
constructor() {
super(app.graph, subgraph, instanceData)
// Set up event listener for promoted widget registration
subgraph.events.addEventListener('widget-promoted', (event) => {
const { widget } = event.detail
// Only handle DOM widgets
if (!isDOMWidget(widget) && !isComponentWidget(widget)) return
const domWidgetStore = useDomWidgetStore()
if (!domWidgetStore.widgetStates.has(widget.id)) {
domWidgetStore.registerWidget(widget)
// Set initial visibility based on whether the widget's node is in the current graph
const widgetState = domWidgetStore.widgetStates.get(widget.id)
if (widgetState) {
const currentGraph = canvasStore.getCanvas().graph
widgetState.visible =
currentGraph?.nodes.includes(widget.node) ?? false
}
}
})
// Set up event listener for promoted widget removal
subgraph.events.addEventListener('widget-demoted', (event) => {
const { widget } = event.detail
// Only handle DOM widgets
if (!isDOMWidget(widget) && !isComponentWidget(widget)) return
const domWidgetStore = useDomWidgetStore()
if (domWidgetStore.widgetStates.has(widget.id)) {
domWidgetStore.unregisterWidget(widget.id)
}
})
this.#setupStrokeStyles()
this.#addInputs(ComfyNode.nodeData.inputs)
this.#addOutputs(ComfyNode.nodeData.outputs)
@@ -95,9 +129,15 @@ export const useLitegraphService = () => {
void extensionService.invokeExtensionsAsync('nodeCreated', this)
this.badges.push(
new LGraphBadge({
text: '',
fgColor: '#dad0de',
bgColor: '#b3b'
text: '',
iconOptions: {
unicode: '\ue96e',
fontFamily: 'PrimeIcons',
color: '#ffffff',
fontSize: 12
},
fgColor: '#ffffff',
bgColor: '#3b82f6'
})
)
}
@@ -107,7 +147,11 @@ export const useLitegraphService = () => {
*/
#setupStrokeStyles() {
this.strokeStyles['running'] = function (this: LGraphNode) {
if (this.id == app.runningNodeId) {
const nodeId = String(this.id)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(nodeId)
const state =
useExecutionStore().nodeLocationProgressStates[nodeLocatorId]?.state
if (state === 'running') {
return { color: '#0f0' }
}
}
@@ -362,7 +406,11 @@ export const useLitegraphService = () => {
*/
#setupStrokeStyles() {
this.strokeStyles['running'] = function (this: LGraphNode) {
if (this.id == app.runningNodeId) {
const nodeId = String(this.id)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(nodeId)
const state =
useExecutionStore().nodeLocationProgressStates[nodeLocatorId]?.state
if (state === 'running') {
return { color: '#0f0' }
}
}
@@ -745,7 +793,7 @@ export const useLitegraphService = () => {
if (isImageNode(this)) {
options.push({
content: 'Open in MaskEditor',
content: 'Open in MaskEditor | Image Canvas',
callback: () => {
ComfyApp.copyToClipspace(this)
// @ts-expect-error fixme ts strict error