Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
eb97685
WIP
Myestery Sep 16, 2025
b836c00
Merge branch 'main' into vuenodes/audio-widgets
Myestery Sep 17, 2025
49edb99
[auto-fix] Apply ESLint and Prettier fixes
actions-user Sep 17, 2025
0fc42fd
Asset Browser Modal Component (#5607)
arjansingh Sep 17, 2025
87bf959
WIP
Myestery Sep 18, 2025
0c4e5de
Add desktop dialogs framework (#5605)
webfiltered Sep 18, 2025
fad5bd4
Line Selection toolbox up with Vue Nodes (#5601)
Myestery Sep 18, 2025
d7a59f4
Move VueFire persistence configuration to initialization (#5614)
christian-byrne Sep 18, 2025
5741e70
LazyImage on Safari (#5626)
Myestery Sep 18, 2025
e5d40db
lint: add tsconfig for browser_tests, fix existing violations (#5633)
DrJKL Sep 18, 2025
bec4710
refactor: Change manager flag from --disable-manager to --enable-mana…
viva-jinyi Sep 18, 2025
8a680c4
[test] Add component test for image compare widget (#5549)
christian-byrne Sep 18, 2025
19dfc37
Explicitly add email scope for social auth login. (#5638)
robinjhuang Sep 18, 2025
3bfcab8
wip
Myestery Sep 19, 2025
2229715
feat: Add recording button and update audio controls in WidgetRecordA…
Myestery Sep 23, 2025
24dd10a
Merge branch 'main' into vuenodes/audio-widgets
Myestery Sep 23, 2025
9766b70
Merge branch 'main' into vuenodes/audio-widgets
Myestery Sep 23, 2025
4b874eb
feat: Enhance WidgetRecordAudio component with improved waveform visu…
Myestery Sep 23, 2025
e3325bb
fix: Correct node data binding in NodeWidgets
Myestery Sep 23, 2025
f9b5219
feat: Refactor audio recording and playback logic in WidgetRecordAudi…
Myestery Sep 23, 2025
0c4f0af
reset unused files
Myestery Sep 23, 2025
b9de771
Preview audio on preview audio nodes
Myestery Sep 24, 2025
aa2858a
feat: Add serializeValue function for audio recording upload and work…
Myestery Sep 24, 2025
9ccd466
feat: Add 'AUDIO_UPLOAD' alias to audio widget definitions
Myestery Sep 24, 2025
816c269
feat: Enhance audio widget serialization and support for Vue components
Myestery Sep 24, 2025
52475d1
feat: Enhance audio widget handling with dynamic sub-widget routing a…
Myestery Sep 24, 2025
d8b62b3
feat: Improve audio recording handling with enhanced state management…
Myestery Sep 24, 2025
9f5e0ae
feat: Update model value to enhance reactivity and parent updates on …
Myestery Sep 24, 2025
660cae6
feat: Enhance upload audio extension with Vue node compatibility and …
Myestery Sep 24, 2025
e597fe2
feat: Add support for hiding specific widgets based on node type
Myestery Sep 24, 2025
69b6f09
feat: Remove console logs from audio setup and recording processes fo…
Myestery Sep 24, 2025
bbb4d0d
Merge branch 'main' into vuenodes/audio-widgets
Myestery Sep 24, 2025
3b3ec1b
WIP
Myestery Sep 25, 2025
83fc740
feat: Implement audio widget factories for Vue and LiteGraph, enhanci…
Myestery Sep 25, 2025
cb5d9aa
Merge remote-tracking branch 'origin/main' into vuenodes/audio-widgets
Myestery Sep 27, 2025
9db0ab6
Merge remote-tracking branch 'origin/main' into vuenodes/audio-widgets
Myestery Sep 29, 2025
a4529cb
reset uploadAudio
Myestery Sep 29, 2025
681a7d3
refactor: streamline audio widget UI and integrate audio preview player
Myestery Sep 29, 2025
08ea852
refactor: remove hidden widgets logic and update widget value directl…
Myestery Sep 29, 2025
c5b1d29
refactor: simplify audioUI aliases by removing redundant entries
Myestery Sep 29, 2025
7a69196
feat: implement audio preview player with playback controls and volum…
Myestery Sep 29, 2025
2c1199a
reset NodeWidgets
Myestery Sep 29, 2025
e745b64
WIP Record Audio Serialization
Myestery Sep 29, 2025
55537e3
refactor: remove unused audio widget factories and serialization store
Myestery Sep 29, 2025
99d1d9d
refactor: remove AudioWidgetFactory and update related imports in aud…
Myestery Sep 29, 2025
c6f8a4b
refactor: move audio utility functions to audioUtils and update imports
Myestery Sep 29, 2025
e5d05ef
refactor: clean up WidgetAudioUI and WidgetRecordAudio components by …
Myestery Sep 29, 2025
698cd4e
refactor: implement audio recording and playback composables for impr…
Myestery Sep 29, 2025
969ad18
refactor: add new audio playback states and messages to localization …
Myestery Sep 29, 2025
b7bc2f9
refactor: change exported interfaces to internal in audio composables…
Myestery Sep 29, 2025
ff48736
Merge branch 'main' into vuenodes/audio-widgets
Myestery Sep 29, 2025
1fde348
refactor: add mock implementation for Worker in vitest setup
Myestery Sep 29, 2025
e32eb7e
Merge remote-tracking branch 'origin/main' into vuenodes/audio-widgets
Myestery Oct 2, 2025
64a0753
fix ts types for audio UI
Myestery Oct 2, 2025
cbf24f2
refactor: update audio widget name based on vueNodesMode
Myestery Oct 2, 2025
3390798
feat: enhance getComponent to support widget name for combo types
Myestery Oct 2, 2025
56261ce
feat: add playback speed and volume controls to audio player
Myestery Oct 2, 2025
66c4a4b
feat: update button styling and refine widget type definition in Widg…
Myestery Oct 2, 2025
dfc0625
[automated] Apply ESLint and Prettier fixes
actions-user Oct 2, 2025
500357b
feat: add IAudioRecordWidget and IAudioPlayerWidget types to widget d…
Myestery Oct 4, 2025
bf53c0e
feat: enhance AudioPreviewPlayer with dynamic audio URL handling and …
Myestery Oct 4, 2025
75e182e
feat: simplify WidgetAudioUI by removing unused components and logic …
Myestery Oct 4, 2025
f5aa102
feat: add audio record widget and input specification to widget regis…
Myestery Oct 4, 2025
48af3f6
feat: implement useAudioRecordWidget composable for audio recording f…
Myestery Oct 4, 2025
3f78e66
feat: remove IAudioPlayerWidget interface from widget definitions
Myestery Oct 6, 2025
4ebf49d
Merge remote-tracking branch 'origin/main' into vuenodes/audio-widgets
Myestery Oct 6, 2025
33e7bcd
refactor: replace comboWidgetAdditions with a function to retrieve wi…
Myestery Oct 6, 2025
d8d730f
fix control button styles
Myestery Oct 6, 2025
889b152
use better ts types
Myestery Oct 6, 2025
16dabb3
refactor: rename playback timer variable for consistency
Myestery Oct 6, 2025
8fb7158
replace useTimer with vueuse useCountdown
Myestery Oct 9, 2025
9274fad
fix: enable canvasOnly option for audioUIWidget
Myestery Oct 9, 2025
ce4fc72
refactor: remove unused pause function from useAudioPlayback and add …
Myestery Oct 9, 2025
306bfba
refactor: use css tokens
Myestery Oct 9, 2025
c6f90c8
useIntervalFn and simplify composables
Myestery Oct 9, 2025
61d8704
chore: reset pnpm files to origin/main
Myestery Oct 9, 2025
f6d460c
Merge remote-tracking branch 'origin/main' into vuenodes/audio-widgets
Myestery Oct 9, 2025
014acc2
reset pnpm-workspace
Myestery Oct 9, 2025
3352eba
[automated] Apply ESLint and Prettier fixes
actions-user Oct 9, 2025
e97ab66
Merge branch 'main' into vuenodes/audio-widgets
Myestery Oct 9, 2025
f28df6d
add LOD support
Myestery Oct 9, 2025
286a73b
[automated] Update test expectations
invalid-email-address Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 6 additions & 29 deletions src/extensions/core/uploadAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import type {
IStringWidget
} from '@/lib/litegraph/src/types/widgets'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type { ResultItemType } from '@/schemas/apiSchema'
import {
getResourceURL,
splitFilePath
} from '@/renderer/extensions/vueNodes/widgets/utils/audioUtils'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { DOMWidget } from '@/scripts/domWidget'
import { useAudioService } from '@/services/audioService'
Expand All @@ -21,32 +24,6 @@ import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
import { api } from '../../scripts/api'
import { app } from '../../scripts/app'

function splitFilePath(path: string): [string, string] {
const folder_separator = path.lastIndexOf('/')
if (folder_separator === -1) {
return ['', path]
}
return [
path.substring(0, folder_separator),
path.substring(folder_separator + 1)
]
}

function getResourceURL(
subfolder: string,
filename: string,
type: ResultItemType = 'input'
): string {
const params = [
'filename=' + encodeURIComponent(filename),
'type=' + type,
'subfolder=' + subfolder,
app.getRandParam().substring(1)
].join('&')

return `/view?${params}`
}

async function uploadFile(
audioWidget: IStringWidget,
audioUIWidget: DOMWidget<HTMLAudioElement, string>,
Expand Down Expand Up @@ -123,7 +100,6 @@ app.registerExtension({
const audioUIWidget: DOMWidget<HTMLAudioElement, string> =
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
audioUIWidget.serialize = false

const { nodeData } = node.constructor
if (nodeData == null) throw new TypeError('nodeData is null')

Expand Down Expand Up @@ -199,6 +175,7 @@ app.registerExtension({
const audioUIWidget = node.widgets.find(
(w) => w.name === 'audioUI'
) as unknown as DOMWidget<HTMLAudioElement, string>
audioUIWidget.options.canvasOnly = true

const onAudioWidgetUpdate = () => {
audioUIWidget.element.src = api.apiURL(
Expand Down Expand Up @@ -273,9 +250,9 @@ app.registerExtension({
audio.controls = true
audio.classList.add('comfy-audio')
audio.setAttribute('name', 'media')

const audioUIWidget: DOMWidget<HTMLAudioElement, string> =
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
audioUIWidget.options.canvasOnly = true

let mediaRecorder: MediaRecorder | null = null
let isRecording = false
Expand Down
6 changes: 6 additions & 0 deletions src/lib/litegraph/src/types/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export type IWidget =
| ISelectButtonWidget
| ITextareaWidget
| IAssetWidget
| IAudioRecordWidget

export interface IBooleanWidget extends IBaseWidget<boolean, 'toggle'> {
type: 'toggle'
Expand Down Expand Up @@ -227,6 +228,11 @@ export interface ITextareaWidget extends IBaseWidget<string, 'textarea'> {
value: string
}

export interface IAudioRecordWidget extends IBaseWidget<string, 'audiorecord'> {
type: 'audiorecord'
value: string
}

export interface IAssetWidget
extends IBaseWidget<string, 'asset', IWidgetOptions<string[]>> {
type: 'asset'
Expand Down
12 changes: 11 additions & 1 deletion src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,17 @@
"nodeHeaderError": "Node Header Error",
"nodeSlotsError": "Node Slots Error",
"nodeWidgetsError": "Node Widgets Error",
"frameNodes": "Frame Nodes"
"frameNodes": "Frame Nodes",
"listening": "Listening...",
"ready": "Ready",
"playRecording": "Play Recording",
"playing": "Playing",
"stopPlayback": "Stop Playback",
"playbackSpeed": "Playback Speed",
"volume": "Volume",
"halfSpeed": "0.5x",
"1x": "1x",
"2x": "2x"
},
"manager": {
"title": "Custom Nodes Manager",
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/extensions/vueNodes/components/NodeWidgets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,14 @@ const processedWidgets = computed((): ProcessedWidget[] => {
const result: ProcessedWidget[] = []

for (const widget of widgets) {
// Skip if widget is in the hidden list for this node type
if (widget.options?.hidden) continue
if (widget.options?.canvasOnly) continue
if (!widget.type) continue
if (!shouldRenderAsVue(widget)) continue

const vueComponent = getComponent(widget.type) || WidgetInputText
const vueComponent =
getComponent(widget.type, widget.name) || WidgetInputText

const slotMetadata = widget.slotMetadata

Expand All @@ -150,6 +152,9 @@ const processedWidgets = computed((): ProcessedWidget[] => {
}

const updateHandler = (value: unknown) => {
// Update the widget value directly
widget.value = value as WidgetValue

if (widget.callback) {
widget.callback(value)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div class="w-full">
<WidgetSelect v-model="modelValue" :widget />
<div class="my-4">
<AudioPreviewPlayer
:audio-url="audioUrlFromWidget"
:readonly="readonly"
:hide-when-empty="isOutputNodeRef"
:show-options-button="true"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import { isOutputNode } from '@/utils/nodeFilterUtil'

import { getAudioUrlFromPath } from '../utils/audioUtils'
import WidgetSelect from './WidgetSelect.vue'
import AudioPreviewPlayer from './audio/AudioPreviewPlayer.vue'

const props = defineProps<{
widget: SimplifiedWidget<string | number | undefined>
readonly?: boolean
nodeId: string
}>()

const modelValue = defineModel<string>('modelValue')

defineEmits<{
'update:modelValue': [value: string]
}>()

// Get litegraph node
const litegraphNode = computed(() => {
if (!props.nodeId || !app.rootGraph) return null
return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null
})

// Check if this is an output node (PreviewAudio, SaveAudio, etc)
const isOutputNodeRef = computed(() => {
const node = litegraphNode.value
if (!node) return false
return isOutputNode(node)
})

const audioFilePath = computed(() => props.widget.value as string)

// Computed audio URL from widget value (for input files)
const audioUrlFromWidget = computed(() => {
const path = audioFilePath.value
if (!path) return ''
return getAudioUrlFromPath(path, 'input')
})
</script>
Loading