mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(lora-cycler): disable pause button when prompts are queued
- Add `hasQueuedPrompts` reactive flag to track queued executions - Pass `is-pause-disabled` prop to settings view to disable pause button - Update pause button title to indicate why it's disabled - Remove server queue clearing logic from pause toggle handler - Clear `hasQueuedPrompts` flag when manually changing index or resetting - Set `hasQueuedPrompts` to true when adding prompts to execution queue - Update flag when processing queued executions to reflect current queue state
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
:repeat-count="state.repeatCount.value"
|
||||
:repeat-used="state.displayRepeatUsed.value"
|
||||
:is-paused="state.isPaused.value"
|
||||
:is-pause-disabled="hasQueuedPrompts"
|
||||
:is-workflow-executing="state.isWorkflowExecuting.value"
|
||||
:executing-repeat-step="state.executingRepeatStep.value"
|
||||
@update:current-index="handleIndexUpdate"
|
||||
@@ -60,6 +61,9 @@ interface ExecutionContext {
|
||||
}
|
||||
const executionQueue: ExecutionContext[] = []
|
||||
|
||||
// Reactive flag to track if there are queued prompts (for disabling pause button)
|
||||
const hasQueuedPrompts = ref(false)
|
||||
|
||||
// Track pending executions for batch queue support (deferred UI updates)
|
||||
// Uses FIFO order since executions are processed in the order they were queued
|
||||
interface PendingExecution {
|
||||
@@ -102,6 +106,7 @@ const handleIndexUpdate = async (newIndex: number) => {
|
||||
|
||||
// Clear execution queue since user is manually changing state
|
||||
executionQueue.length = 0
|
||||
hasQueuedPrompts.value = false
|
||||
|
||||
state.setIndex(newIndex)
|
||||
|
||||
@@ -149,39 +154,9 @@ const handleRepeatCountChange = (newValue: number) => {
|
||||
state.displayRepeatUsed.value = 0
|
||||
}
|
||||
|
||||
// Clear all pending items from server queue
|
||||
const clearPendingQueue = async () => {
|
||||
try {
|
||||
// Clear local execution queue
|
||||
executionQueue.length = 0
|
||||
|
||||
// Clear server queue (pending items only)
|
||||
await fetch('/queue', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ clear: true })
|
||||
})
|
||||
|
||||
console.log('[LoraCyclerWidget] Cleared pending queue on pause')
|
||||
} catch (error) {
|
||||
console.error('[LoraCyclerWidget] Error clearing queue:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle pause toggle
|
||||
const handleTogglePause = async () => {
|
||||
const wasPaused = state.isPaused.value
|
||||
const handleTogglePause = () => {
|
||||
state.togglePause()
|
||||
|
||||
// When transitioning to paused state, clear pending queue
|
||||
if (!wasPaused && state.isPaused.value) {
|
||||
// Reset execution state so subsequent manual queues start fresh
|
||||
;(props.widget as any)[HAS_EXECUTED] = false
|
||||
state.executionIndex.value = null
|
||||
state.nextIndex.value = null
|
||||
|
||||
await clearPendingQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle reset index
|
||||
@@ -193,6 +168,7 @@ const handleResetIndex = async () => {
|
||||
|
||||
// Clear execution queue since user is resetting state
|
||||
executionQueue.length = 0
|
||||
hasQueuedPrompts.value = false
|
||||
|
||||
// Reset index and repeat state
|
||||
state.resetIndex()
|
||||
@@ -265,6 +241,7 @@ onMounted(async () => {
|
||||
shouldAdvanceDisplay: false,
|
||||
displayRepeatUsed: state.displayRepeatUsed.value // Keep current display value when paused
|
||||
})
|
||||
hasQueuedPrompts.value = true
|
||||
// CRITICAL: Clear execution_index when paused to force backend to use current_index
|
||||
// This ensures paused executions use the same LoRA regardless of any
|
||||
// execution_index set by previous non-paused beforeQueued calls
|
||||
@@ -308,6 +285,7 @@ onMounted(async () => {
|
||||
shouldAdvanceDisplay,
|
||||
displayRepeatUsed
|
||||
})
|
||||
hasQueuedPrompts.value = true
|
||||
|
||||
// Update the widget value so the indices are included in the serialized config
|
||||
props.widget.value = state.buildConfig()
|
||||
@@ -334,6 +312,7 @@ onMounted(async () => {
|
||||
|
||||
// Pop execution context from queue (FIFO order)
|
||||
const context = executionQueue.shift()
|
||||
hasQueuedPrompts.value = executionQueue.length > 0
|
||||
|
||||
// Determine if we should advance the display index
|
||||
const shouldAdvanceDisplay = context
|
||||
|
||||
@@ -87,8 +87,9 @@
|
||||
<button
|
||||
class="control-btn"
|
||||
:class="{ active: isPaused }"
|
||||
:disabled="isPauseDisabled"
|
||||
@click="$emit('toggle-pause')"
|
||||
:title="isPaused ? 'Continue iteration' : 'Pause iteration'"
|
||||
:title="isPauseDisabled ? 'Cannot pause while prompts are queued' : (isPaused ? 'Continue iteration' : 'Pause iteration')"
|
||||
>
|
||||
<svg v-if="isPaused" viewBox="0 0 24 24" fill="currentColor" class="control-icon">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
@@ -175,6 +176,7 @@ const props = defineProps<{
|
||||
repeatCount: number
|
||||
repeatUsed: number
|
||||
isPaused: boolean
|
||||
isPauseDisabled: boolean
|
||||
isWorkflowExecuting: boolean
|
||||
executingRepeatStep: number
|
||||
}>()
|
||||
@@ -504,12 +506,17 @@ const onRepeatBlur = (event: Event) => {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
.control-btn:hover:not(:disabled) {
|
||||
background: rgba(66, 153, 225, 0.2);
|
||||
border-color: rgba(66, 153, 225, 0.4);
|
||||
color: rgba(191, 219, 254, 1);
|
||||
}
|
||||
|
||||
.control-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.control-btn.active {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
border-color: rgba(245, 158, 11, 0.5);
|
||||
|
||||
@@ -497,64 +497,39 @@ describe('Batch Queue Integration Tests', () => {
|
||||
// What matters is widget.value which is what the backend uses
|
||||
})
|
||||
|
||||
it('should clear server queue when pausing mid-batch', async () => {
|
||||
// This tests the fix for the batch queue pause bug:
|
||||
// When user presses pause during batch execution, pending queue items should be cleared
|
||||
it('should have hasQueuedPrompts true when execution queue has items', async () => {
|
||||
// This tests the pause button disabled state
|
||||
const harness = createTestHarness({ totalCount: 5 })
|
||||
|
||||
// Initially no queued prompts
|
||||
expect(harness.executionQueue.length).toBe(0)
|
||||
|
||||
// Queue some prompts
|
||||
harness.widget.beforeQueued()
|
||||
harness.widget.beforeQueued()
|
||||
harness.widget.beforeQueued()
|
||||
|
||||
// Execution queue should have items
|
||||
expect(harness.executionQueue.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should have empty execution queue after all executions complete', async () => {
|
||||
// This tests that pause button becomes enabled after executions complete
|
||||
const harness = createTestHarness({ totalCount: 5 })
|
||||
const simulator = new BatchQueueSimulator({ totalCount: 5 })
|
||||
|
||||
// Mock fetch to track calls to /queue
|
||||
const fetchCalls: { url: string; body: any }[] = []
|
||||
const originalFetch = global.fetch
|
||||
global.fetch = vi.fn().mockImplementation((url: string, options?: RequestInit) => {
|
||||
if (url === '/queue') {
|
||||
fetchCalls.push({ url, body: options?.body ? JSON.parse(options.body as string) : null })
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
|
||||
}
|
||||
// Call through for other URLs (like cycler-list API)
|
||||
return originalFetch(url, options)
|
||||
}) as any
|
||||
// Run batch queue execution
|
||||
await simulator.runBatchQueue(
|
||||
3,
|
||||
{
|
||||
beforeQueued: () => harness.widget.beforeQueued(),
|
||||
onExecuted: (output) => harness.node.onExecuted(output)
|
||||
},
|
||||
() => harness.getConfig()
|
||||
)
|
||||
|
||||
try {
|
||||
// Queue 4 prompts while not paused
|
||||
harness.widget.beforeQueued()
|
||||
harness.widget.beforeQueued()
|
||||
harness.widget.beforeQueued()
|
||||
harness.widget.beforeQueued()
|
||||
|
||||
// Verify 4 contexts were queued
|
||||
expect(harness.executionQueue.length).toBe(4)
|
||||
|
||||
// Simulate pressing pause (this is what handleTogglePause does in the component)
|
||||
const wasPaused = harness.state.isPaused.value
|
||||
harness.state.togglePause()
|
||||
|
||||
// When transitioning to paused, the component should:
|
||||
// 1. Reset execution state
|
||||
// 2. Clear execution queue
|
||||
// 3. Call fetch('/queue', { clear: true })
|
||||
if (!wasPaused && harness.state.isPaused.value) {
|
||||
// Reset execution state (mimics component behavior)
|
||||
harness.resetExecutionState()
|
||||
|
||||
// Clear server queue (mimics component behavior)
|
||||
await fetch('/queue', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ clear: true })
|
||||
})
|
||||
}
|
||||
|
||||
// Verify execution queue was cleared
|
||||
// After all executions, queue should be empty
|
||||
expect(harness.executionQueue.length).toBe(0)
|
||||
|
||||
// Verify fetch was called with correct parameters
|
||||
expect(fetchCalls.length).toBe(1)
|
||||
expect(fetchCalls[0].url).toBe('/queue')
|
||||
expect(fetchCalls[0].body).toEqual({ clear: true })
|
||||
} finally {
|
||||
global.fetch = originalFetch
|
||||
}
|
||||
})
|
||||
|
||||
it('should resume cycling after unpause', async () => {
|
||||
|
||||
@@ -1464,16 +1464,16 @@ to { transform: rotate(360deg);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cycler-settings[data-v-3831ffe1] {
|
||||
.cycler-settings[data-v-9be19e47] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
color: #e4e4e7;
|
||||
}
|
||||
.settings-header[data-v-3831ffe1] {
|
||||
.settings-header[data-v-9be19e47] {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.settings-title[data-v-3831ffe1] {
|
||||
.settings-title[data-v-9be19e47] {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
@@ -1482,10 +1482,10 @@ to { transform: rotate(360deg);
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.setting-section[data-v-3831ffe1] {
|
||||
.setting-section[data-v-9be19e47] {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.setting-label[data-v-3831ffe1] {
|
||||
.setting-label[data-v-9be19e47] {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
@@ -1494,10 +1494,10 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Progress Display */
|
||||
.progress-section[data-v-3831ffe1] {
|
||||
.progress-section[data-v-9be19e47] {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.progress-display[data-v-3831ffe1] {
|
||||
.progress-display[data-v-9be19e47] {
|
||||
background: rgba(26, 32, 44, 0.9);
|
||||
border: 1px solid rgba(226, 232, 240, 0.2);
|
||||
border-radius: 6px;
|
||||
@@ -1507,31 +1507,31 @@ to { transform: rotate(360deg);
|
||||
align-items: center;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.progress-display.executing[data-v-3831ffe1] {
|
||||
.progress-display.executing[data-v-9be19e47] {
|
||||
border-color: rgba(66, 153, 225, 0.5);
|
||||
animation: pulse-3831ffe1 2s ease-in-out infinite;
|
||||
animation: pulse-9be19e47 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-3831ffe1 {
|
||||
@keyframes pulse-9be19e47 {
|
||||
0%, 100% { border-color: rgba(66, 153, 225, 0.3);
|
||||
}
|
||||
50% { border-color: rgba(66, 153, 225, 0.7);
|
||||
}
|
||||
}
|
||||
.progress-info[data-v-3831ffe1] {
|
||||
.progress-info[data-v-9be19e47] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.progress-label[data-v-3831ffe1] {
|
||||
.progress-label[data-v-9be19e47] {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: rgba(226, 232, 240, 0.5);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.progress-name[data-v-3831ffe1] {
|
||||
.progress-name[data-v-9be19e47] {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(191, 219, 254, 1);
|
||||
@@ -1539,14 +1539,14 @@ to { transform: rotate(360deg);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.progress-counter[data-v-3831ffe1] {
|
||||
.progress-counter[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-left: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.progress-index[data-v-3831ffe1] {
|
||||
.progress-index[data-v-9be19e47] {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: rgba(66, 153, 225, 1);
|
||||
@@ -1555,12 +1555,12 @@ to { transform: rotate(360deg);
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.progress-separator[data-v-3831ffe1] {
|
||||
.progress-separator[data-v-9be19e47] {
|
||||
font-size: 14px;
|
||||
color: rgba(226, 232, 240, 0.4);
|
||||
margin: 0 2px;
|
||||
}
|
||||
.progress-total[data-v-3831ffe1] {
|
||||
.progress-total[data-v-9be19e47] {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
@@ -1569,7 +1569,7 @@ to { transform: rotate(360deg);
|
||||
text-align: left;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.refresh-button[data-v-3831ffe1] {
|
||||
.refresh-button[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1584,23 +1584,23 @@ to { transform: rotate(360deg);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.refresh-button[data-v-3831ffe1]:hover:not(:disabled) {
|
||||
.refresh-button[data-v-9be19e47]:hover:not(:disabled) {
|
||||
background: rgba(66, 153, 225, 0.2);
|
||||
border-color: rgba(66, 153, 225, 0.4);
|
||||
color: rgba(191, 219, 254, 1);
|
||||
}
|
||||
.refresh-button[data-v-3831ffe1]:disabled {
|
||||
.refresh-button[data-v-9be19e47]:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.refresh-icon[data-v-3831ffe1] {
|
||||
.refresh-icon[data-v-9be19e47] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.refresh-icon.spinning[data-v-3831ffe1] {
|
||||
animation: spin-3831ffe1 1s linear infinite;
|
||||
.refresh-icon.spinning[data-v-9be19e47] {
|
||||
animation: spin-9be19e47 1s linear infinite;
|
||||
}
|
||||
@keyframes spin-3831ffe1 {
|
||||
@keyframes spin-9be19e47 {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@@ -1610,7 +1610,7 @@ to {
|
||||
}
|
||||
|
||||
/* Repeat Badge */
|
||||
.repeat-badge[data-v-3831ffe1] {
|
||||
.repeat-badge[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -1620,12 +1620,12 @@ to {
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.repeat-badge-label[data-v-3831ffe1] {
|
||||
.repeat-badge-label[data-v-9be19e47] {
|
||||
font-size: 10px;
|
||||
color: rgba(253, 230, 138, 0.7);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.repeat-badge-value[data-v-3831ffe1] {
|
||||
.repeat-badge-value[data-v-9be19e47] {
|
||||
font-size: 12px;
|
||||
font-family: 'SF Mono', 'Roboto Mono', monospace;
|
||||
color: rgba(253, 230, 138, 1);
|
||||
@@ -1634,13 +1634,13 @@ to {
|
||||
}
|
||||
|
||||
/* Index Controls Row */
|
||||
.index-controls-row[data-v-3831ffe1] {
|
||||
.index-controls-row[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.index-input[data-v-3831ffe1] {
|
||||
.index-input[data-v-9be19e47] {
|
||||
width: 60px;
|
||||
padding: 6px 8px;
|
||||
background: rgba(26, 32, 44, 0.9);
|
||||
@@ -1650,15 +1650,15 @@ to {
|
||||
font-size: 13px;
|
||||
font-family: 'SF Mono', 'Roboto Mono', monospace;
|
||||
}
|
||||
.index-input[data-v-3831ffe1]:focus {
|
||||
.index-input[data-v-9be19e47]:focus {
|
||||
outline: none;
|
||||
border-color: rgba(66, 153, 225, 0.6);
|
||||
}
|
||||
.index-input[data-v-3831ffe1]:disabled {
|
||||
.index-input[data-v-9be19e47]:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.index-hint[data-v-3831ffe1] {
|
||||
.index-hint[data-v-9be19e47] {
|
||||
font-size: 11px;
|
||||
color: rgba(226, 232, 240, 0.4);
|
||||
min-width: 7ch;
|
||||
@@ -1666,12 +1666,12 @@ to {
|
||||
}
|
||||
|
||||
/* Repeat Controls */
|
||||
.repeat-label[data-v-3831ffe1] {
|
||||
.repeat-label[data-v-9be19e47] {
|
||||
font-size: 13px;
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
margin-left: 4px;
|
||||
}
|
||||
.repeat-input[data-v-3831ffe1] {
|
||||
.repeat-input[data-v-9be19e47] {
|
||||
width: 44px;
|
||||
padding: 6px 6px;
|
||||
background: rgba(26, 32, 44, 0.9);
|
||||
@@ -1682,17 +1682,17 @@ to {
|
||||
font-family: 'SF Mono', 'Roboto Mono', monospace;
|
||||
text-align: center;
|
||||
}
|
||||
.repeat-input[data-v-3831ffe1]:focus {
|
||||
.repeat-input[data-v-9be19e47]:focus {
|
||||
outline: none;
|
||||
border-color: rgba(66, 153, 225, 0.6);
|
||||
}
|
||||
.repeat-hint[data-v-3831ffe1] {
|
||||
.repeat-hint[data-v-9be19e47] {
|
||||
font-size: 11px;
|
||||
color: rgba(226, 232, 240, 0.4);
|
||||
}
|
||||
|
||||
/* Control Buttons */
|
||||
.control-btn[data-v-3831ffe1] {
|
||||
.control-btn[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1706,48 +1706,52 @@ to {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.control-btn[data-v-3831ffe1]:hover {
|
||||
.control-btn[data-v-9be19e47]:hover:not(:disabled) {
|
||||
background: rgba(66, 153, 225, 0.2);
|
||||
border-color: rgba(66, 153, 225, 0.4);
|
||||
color: rgba(191, 219, 254, 1);
|
||||
}
|
||||
.control-btn.active[data-v-3831ffe1] {
|
||||
.control-btn[data-v-9be19e47]:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.control-btn.active[data-v-9be19e47] {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
border-color: rgba(245, 158, 11, 0.5);
|
||||
color: rgba(253, 230, 138, 1);
|
||||
}
|
||||
.control-btn.active[data-v-3831ffe1]:hover {
|
||||
.control-btn.active[data-v-9be19e47]:hover {
|
||||
background: rgba(245, 158, 11, 0.3);
|
||||
border-color: rgba(245, 158, 11, 0.6);
|
||||
}
|
||||
.control-icon[data-v-3831ffe1] {
|
||||
.control-icon[data-v-9be19e47] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
/* Slider Container */
|
||||
.slider-container[data-v-3831ffe1] {
|
||||
.slider-container[data-v-9be19e47] {
|
||||
background: rgba(26, 32, 44, 0.9);
|
||||
border: 1px solid rgba(226, 232, 240, 0.2);
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
.slider-container--disabled[data-v-3831ffe1] {
|
||||
.slider-container--disabled[data-v-9be19e47] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.section-header-with-toggle[data-v-3831ffe1] {
|
||||
.section-header-with-toggle[data-v-9be19e47] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.section-header-with-toggle .setting-label[data-v-3831ffe1] {
|
||||
.section-header-with-toggle .setting-label[data-v-9be19e47] {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle-switch[data-v-3831ffe1] {
|
||||
.toggle-switch[data-v-9be19e47] {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
@@ -1756,7 +1760,7 @@ to {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-switch__track[data-v-3831ffe1] {
|
||||
.toggle-switch__track[data-v-9be19e47] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--comfy-input-bg, #333);
|
||||
@@ -1764,11 +1768,11 @@ to {
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.toggle-switch--active .toggle-switch__track[data-v-3831ffe1] {
|
||||
.toggle-switch--active .toggle-switch__track[data-v-9be19e47] {
|
||||
background: rgba(66, 153, 225, 0.3);
|
||||
border-color: rgba(66, 153, 225, 0.6);
|
||||
}
|
||||
.toggle-switch__thumb[data-v-3831ffe1] {
|
||||
.toggle-switch__thumb[data-v-9be19e47] {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
@@ -1779,16 +1783,16 @@ to {
|
||||
transition: all 0.2s;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.toggle-switch--active .toggle-switch__thumb[data-v-3831ffe1] {
|
||||
.toggle-switch--active .toggle-switch__thumb[data-v-9be19e47] {
|
||||
transform: translateX(16px);
|
||||
background: #4299e1;
|
||||
opacity: 1;
|
||||
}
|
||||
.toggle-switch:hover .toggle-switch__thumb[data-v-3831ffe1] {
|
||||
.toggle-switch:hover .toggle-switch__thumb[data-v-9be19e47] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lora-cycler-widget[data-v-e8c2adc1] {
|
||||
.lora-cycler-widget[data-v-9c17d0bf] {
|
||||
padding: 6px;
|
||||
background: rgba(40, 44, 52, 0.6);
|
||||
border-radius: 6px;
|
||||
@@ -12824,7 +12828,7 @@ const _hoisted_13 = { class: "index-controls-row" };
|
||||
const _hoisted_14 = ["max", "value", "disabled"];
|
||||
const _hoisted_15 = { class: "index-hint" };
|
||||
const _hoisted_16 = ["value"];
|
||||
const _hoisted_17 = ["title"];
|
||||
const _hoisted_17 = ["disabled", "title"];
|
||||
const _hoisted_18 = {
|
||||
key: 0,
|
||||
viewBox: "0 0 24 24",
|
||||
@@ -12858,6 +12862,7 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
||||
repeatCount: {},
|
||||
repeatUsed: {},
|
||||
isPaused: { type: Boolean },
|
||||
isPauseDisabled: { type: Boolean },
|
||||
isWorkflowExecuting: { type: Boolean },
|
||||
executingRepeatStep: {}
|
||||
},
|
||||
@@ -12985,8 +12990,9 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
||||
_cache[19] || (_cache[19] = createBaseVNode("span", { class: "repeat-hint" }, "times", -1)),
|
||||
createBaseVNode("button", {
|
||||
class: normalizeClass(["control-btn", { active: __props.isPaused }]),
|
||||
disabled: __props.isPauseDisabled,
|
||||
onClick: _cache[7] || (_cache[7] = ($event) => _ctx.$emit("toggle-pause")),
|
||||
title: __props.isPaused ? "Continue iteration" : "Pause iteration"
|
||||
title: __props.isPauseDisabled ? "Cannot pause while prompts are queued" : __props.isPaused ? "Continue iteration" : "Pause iteration"
|
||||
}, [
|
||||
__props.isPaused ? (openBlock(), createElementBlock("svg", _hoisted_18, [..._cache[15] || (_cache[15] = [
|
||||
createBaseVNode("path", { d: "M8 5v14l11-7z" }, null, -1)
|
||||
@@ -13055,7 +13061,7 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
||||
};
|
||||
}
|
||||
});
|
||||
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-3831ffe1"]]);
|
||||
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-9be19e47"]]);
|
||||
function useLoraCyclerState(widget) {
|
||||
let isRestoring = false;
|
||||
const currentIndex = ref(1);
|
||||
@@ -13303,6 +13309,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
const state = useLoraCyclerState(props.widget);
|
||||
const HAS_EXECUTED = Symbol("HAS_EXECUTED");
|
||||
const executionQueue = [];
|
||||
const hasQueuedPrompts = ref(false);
|
||||
const pendingExecutions = [];
|
||||
const lastPoolConfigHash = ref("");
|
||||
const isMounted = ref(false);
|
||||
@@ -13317,6 +13324,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
state.executionIndex.value = null;
|
||||
state.nextIndex.value = null;
|
||||
executionQueue.length = 0;
|
||||
hasQueuedPrompts.value = false;
|
||||
state.setIndex(newIndex);
|
||||
try {
|
||||
const poolConfig = getPoolConfig();
|
||||
@@ -13351,34 +13359,15 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
state.repeatUsed.value = 0;
|
||||
state.displayRepeatUsed.value = 0;
|
||||
};
|
||||
const clearPendingQueue = async () => {
|
||||
try {
|
||||
executionQueue.length = 0;
|
||||
await fetch("/queue", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ clear: true })
|
||||
});
|
||||
console.log("[LoraCyclerWidget] Cleared pending queue on pause");
|
||||
} catch (error) {
|
||||
console.error("[LoraCyclerWidget] Error clearing queue:", error);
|
||||
}
|
||||
};
|
||||
const handleTogglePause = async () => {
|
||||
const wasPaused = state.isPaused.value;
|
||||
const handleTogglePause = () => {
|
||||
state.togglePause();
|
||||
if (!wasPaused && state.isPaused.value) {
|
||||
props.widget[HAS_EXECUTED] = false;
|
||||
state.executionIndex.value = null;
|
||||
state.nextIndex.value = null;
|
||||
await clearPendingQueue();
|
||||
}
|
||||
};
|
||||
const handleResetIndex = async () => {
|
||||
props.widget[HAS_EXECUTED] = false;
|
||||
state.executionIndex.value = null;
|
||||
state.nextIndex.value = null;
|
||||
executionQueue.length = 0;
|
||||
hasQueuedPrompts.value = false;
|
||||
state.resetIndex();
|
||||
try {
|
||||
const poolConfig = getPoolConfig();
|
||||
@@ -13428,6 +13417,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
displayRepeatUsed: state.displayRepeatUsed.value
|
||||
// Keep current display value when paused
|
||||
});
|
||||
hasQueuedPrompts.value = true;
|
||||
const pausedConfig = state.buildConfig();
|
||||
pausedConfig.execution_index = null;
|
||||
props.widget.value = pausedConfig;
|
||||
@@ -13454,6 +13444,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
shouldAdvanceDisplay,
|
||||
displayRepeatUsed
|
||||
});
|
||||
hasQueuedPrompts.value = true;
|
||||
props.widget.value = state.buildConfig();
|
||||
};
|
||||
isMounted.value = true;
|
||||
@@ -13468,6 +13459,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
props.node.onExecuted = function(output) {
|
||||
console.log("[LoraCyclerWidget] Node executed with output:", output);
|
||||
const context = executionQueue.shift();
|
||||
hasQueuedPrompts.value = executionQueue.length > 0;
|
||||
const shouldAdvanceDisplay = context ? context.shouldAdvanceDisplay : !state.isPaused.value && state.repeatUsed.value >= state.repeatCount.value;
|
||||
const nextIndex = (output == null ? void 0 : output.next_index) !== void 0 ? Array.isArray(output.next_index) ? output.next_index[0] : output.next_index : state.currentIndex.value;
|
||||
const nextLoraName = (output == null ? void 0 : output.next_lora_name) !== void 0 ? Array.isArray(output.next_lora_name) ? output.next_lora_name[0] : output.next_lora_name : "";
|
||||
@@ -13557,6 +13549,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
"repeat-count": unref(state).repeatCount.value,
|
||||
"repeat-used": unref(state).displayRepeatUsed.value,
|
||||
"is-paused": unref(state).isPaused.value,
|
||||
"is-pause-disabled": hasQueuedPrompts.value,
|
||||
"is-workflow-executing": unref(state).isWorkflowExecuting.value,
|
||||
"executing-repeat-step": unref(state).executingRepeatStep.value,
|
||||
"onUpdate:currentIndex": handleIndexUpdate,
|
||||
@@ -13567,12 +13560,12 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
onTogglePause: handleTogglePause,
|
||||
onResetIndex: handleResetIndex,
|
||||
onRefresh: handleRefresh
|
||||
}, null, 8, ["current-index", "total-count", "current-lora-name", "current-lora-filename", "model-strength", "clip-strength", "use-custom-clip-range", "is-clip-strength-disabled", "is-loading", "repeat-count", "repeat-used", "is-paused", "is-workflow-executing", "executing-repeat-step"])
|
||||
}, null, 8, ["current-index", "total-count", "current-lora-name", "current-lora-filename", "model-strength", "clip-strength", "use-custom-clip-range", "is-clip-strength-disabled", "is-loading", "repeat-count", "repeat-used", "is-paused", "is-pause-disabled", "is-workflow-executing", "executing-repeat-step"])
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-e8c2adc1"]]);
|
||||
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-9c17d0bf"]]);
|
||||
const _hoisted_1$1 = { class: "json-display-widget" };
|
||||
const _hoisted_2$1 = {
|
||||
class: "json-content",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user