Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions src/composables/useCoreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,13 @@ export function useCoreCommands(): ComfyCommand[] {
await settingStore.set('Comfy.Assets.UseAssetAPI', !current)
await useWorkflowService().reloadCurrentWorkflow() // ensure changes take effect immediately
}
},
{
id: 'Comfy.ToggleLinear',
icon: 'pi pi-database',
label: 'toggle linear mode',
//@ts-expect-error temporary duck violence
function: () => (app.linearMode.value = !app.linearMode.value)
}
]

Expand Down
12 changes: 11 additions & 1 deletion src/stores/imagePreviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
const { nodeIdToNodeLocatorId, nodeToNodeLocatorId } = useWorkflowStore()
const { executionIdToNodeLocatorId } = useExecutionStore()
const scheduledRevoke: Record<NodeLocatorId, { stop: () => void }> = {}
const latestOutput = ref<string[]>([])

function scheduleRevoke(locator: NodeLocatorId, cb: () => void) {
scheduledRevoke[locator]?.stop()
Expand Down Expand Up @@ -146,6 +147,13 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
}
}

//TODO:Preview params and deduplication
latestOutput.value =
(outputs as ExecutedWsMessage['output'])?.images?.map((image) => {
const imgUrlPart = new URLSearchParams(image)
const rand = app.getRandParam()
return api.apiURL(`/view?${imgUrlPart}${rand}`)
}) ?? []
app.nodeOutputs[nodeLocatorId] = outputs
nodeOutputs.value[nodeLocatorId] = outputs
}
Expand Down Expand Up @@ -213,6 +221,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
scheduledRevoke[nodeLocatorId].stop()
delete scheduledRevoke[nodeLocatorId]
}
latestOutput.value = previewImages
app.nodePreviewImages[nodeLocatorId] = previewImages
nodePreviewImages.value[nodeLocatorId] = previewImages
}
Expand Down Expand Up @@ -381,6 +390,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {

// State
nodeOutputs,
nodePreviewImages
nodePreviewImages,
latestOutput
}
})
5 changes: 5 additions & 0 deletions src/views/GraphView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
<div id="comfyui-body-left" class="comfyui-body-left" />
<div id="comfyui-body-right" class="comfyui-body-right" />
<div
v-show="!linearMode"
id="graph-canvas-container"
ref="graphCanvasContainerRef"
class="graph-canvas-container"
>
<GraphCanvas @ready="onGraphReady" />
</div>
<LinearView v-if="linearMode" />
</div>

<GlobalToast />
Expand Down Expand Up @@ -76,6 +78,7 @@ import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI, isElectron } from '@/utils/envUtil'
import LinearView from '@/views/LinearView.vue'

setupAutoQueueHandler()
useProgressFavicon()
Expand All @@ -93,6 +96,8 @@ const queueStore = useQueueStore()
const assetsStore = useAssetsStore()
const versionCompatibilityStore = useVersionCompatibilityStore()
const graphCanvasContainerRef = ref<HTMLDivElement | null>(null)
//@ts-expect-error temporary duck violence
const linearMode = (app.linearMode = ref<boolean>(false))

const telemetry = useTelemetry()
const firebaseAuthStore = useFirebaseAuthStore()
Expand Down
104 changes: 104 additions & 0 deletions src/views/LinearView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import Splitter from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import { computed } from 'vue'

import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
import { useQueueSidebarTab } from '@/composables/sidebarTabs/useQueueSidebarTab'
import { t } from '@/i18n'
import { useTelemetry } from '@/platform/telemetry'
import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'
import WidgetInputNumberInput from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
//import { useQueueStore } from '@/stores/queueStore'
import { useQueueSettingsStore } from '@/stores/queueStore'

//const queueStore = useQueueStore()
const { vueNodeData } = useGraphNodeManager(app.graph)
const nodeOutputStore = useNodeOutputStore()
const commandStore = useCommandStore()
const nodeData = computed(() => vueNodeData?.values().next().value)

const batchCountWidget = {
options: { step2: 1, precision: 1, min: 1, max: 100 },
value: 1,
name: t('Number of generations'),
type: 'number'
}

const { batchCount } = storeToRefs(useQueueSettingsStore())

//TODO: refactor out of this file.
//code length is small, but changes should propogate
async function runButtonClick(e: Event) {
const isShiftPressed = 'shiftKey' in e && e.shiftKey
const commandId = isShiftPressed
? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt'

useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_linear'
})
if (batchCount.value > 1) {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_multiple_batches_submitted'
})
}
await commandStore.execute(commandId, {
metadata: {
subscribe_to_run: false,
trigger_source: 'button'
}
})
}
</script>
<template>
<Splitter class="absolute h-full w-full">
<SplitterPanel :size="1" class="min-w-min">
<div
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto"
>
<ExtensionSlot :extension="useQueueSidebarTab()" />
</div>
</SplitterPanel>
<SplitterPanel
:size="98"
class="flex flex-row overflow-y-auto flex-wrap min-w-min gap-4"
>
<img
v-for="previewUrl in nodeOutputStore.latestOutput"
:key="previewUrl"
class="pointer-events-none object-contain flex-1"
:src="previewUrl"
/>
</SplitterPanel>
<SplitterPanel
:size="1"
class="flex flex-col gap-4 p-4 bg-component-node-background min-w-min"
>
<NodeWidgets :node-data class="overflow-y-auto *:max-h-60" />
<div class="border-t-1 border-node-component-border pt-4 mx-4">
<WidgetInputNumberInput
v-model="batchCount"
:widget="batchCountWidget"
/>
<Button
:label="t('menu.run')"
class="w-full mt-4"
icon="icon-[lucide--play]"
@click="runButtonClick"
/>
</div>
</SplitterPanel>
</Splitter>
</template>
<style scoped>
textarea {
max-height: 200px;
}
</style>