mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
subscription page (#6064)
Summary Implements cloud subscription management UI and flow for ComfyUI Cloud users. Core Features: - Subscription Status Tracking: Global reactive state management for subscription status across all components using shared subscriptionStatus ref - Subscribe to Run Button: Replaces the Run button in the actionbar with a "Subscribe to Run" button for users without active subscriptions - Subscription Required Dialog: Modal dialog with subscription benefits, pricing, and checkout flow with video background - Subscription Settings Panel: New settings panel showing subscription status, renewal date, and quick access to billing management - Auto-detection & Polling: Automatically polls subscription status after checkout completion and syncs state across the application https://github.com/user-attachments/assets/f41b8e6a-5845-48a7-8169-3a6fc0d2e5c8 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6064-subscription-page-28d6d73d36508135a2a0fe7c94b40852) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<component
|
||||
:is="currentButton"
|
||||
:key="isActiveSubscription ? 'queue' : 'subscribe'"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ComfyQueueButton from '@/components/actionbar/ComfyRunButton/ComfyQueueButton.vue'
|
||||
import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
|
||||
const currentButton = computed(() =>
|
||||
isActiveSubscription.value ? ComfyQueueButton : SubscribeToRunButton
|
||||
)
|
||||
</script>
|
||||
157
src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue
Normal file
157
src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="queue-button-group flex">
|
||||
<SplitButton
|
||||
v-tooltip.bottom="{
|
||||
value: workspaceStore.shiftDown
|
||||
? $t('menu.runWorkflowFront')
|
||||
: $t('menu.runWorkflow'),
|
||||
showDelay: 600
|
||||
}"
|
||||
class="comfyui-queue-button"
|
||||
:label="activeQueueModeMenuItem.label"
|
||||
severity="primary"
|
||||
size="small"
|
||||
:model="queueModeMenuItems"
|
||||
data-testid="queue-button"
|
||||
@click="queuePrompt"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
|
||||
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
|
||||
<i
|
||||
v-else-if="queueMode === 'instant'"
|
||||
class="icon-[lucide--fast-forward]"
|
||||
/>
|
||||
<i
|
||||
v-else-if="queueMode === 'change'"
|
||||
class="icon-[lucide--step-forward]"
|
||||
/>
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<Button
|
||||
v-tooltip="{
|
||||
value: item.tooltip,
|
||||
showDelay: 600
|
||||
}"
|
||||
:label="String(item.label)"
|
||||
:icon="item.icon"
|
||||
:severity="item.key === queueMode ? 'primary' : 'secondary'"
|
||||
size="small"
|
||||
text
|
||||
/>
|
||||
</template>
|
||||
</SplitButton>
|
||||
<BatchCountEdit />
|
||||
<ButtonGroup class="execution-actions flex flex-nowrap">
|
||||
<Button
|
||||
v-tooltip.bottom="{
|
||||
value: $t('menu.interrupt'),
|
||||
showDelay: 600
|
||||
}"
|
||||
icon="pi pi-times"
|
||||
:severity="executingPrompt ? 'danger' : 'secondary'"
|
||||
:disabled="!executingPrompt"
|
||||
text
|
||||
:aria-label="$t('menu.interrupt')"
|
||||
@click="() => commandStore.execute('Comfy.Interrupt')"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.bottom="{
|
||||
value: $t('sideToolbar.queueTab.clearPendingTasks'),
|
||||
showDelay: 600
|
||||
}"
|
||||
icon="pi pi-stop"
|
||||
:severity="hasPendingTasks ? 'danger' : 'secondary'"
|
||||
:disabled="!hasPendingTasks"
|
||||
text
|
||||
:aria-label="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
@click="
|
||||
() => {
|
||||
if (queueCountStore.count.value > 1) {
|
||||
commandStore.execute('Comfy.ClearPendingTasks')
|
||||
}
|
||||
queueMode = 'disabled'
|
||||
}
|
||||
"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import ButtonGroup from 'primevue/buttongroup'
|
||||
import SplitButton from 'primevue/splitbutton'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import {
|
||||
useQueuePendingTaskCountStore,
|
||||
useQueueSettingsStore
|
||||
} from '@/stores/queueStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import BatchCountEdit from '../BatchCountEdit.vue'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
|
||||
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
|
||||
|
||||
const { t } = useI18n()
|
||||
const queueModeMenuItemLookup = computed(() => ({
|
||||
disabled: {
|
||||
key: 'disabled',
|
||||
label: t('menu.run'),
|
||||
tooltip: t('menu.disabledTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'disabled'
|
||||
}
|
||||
},
|
||||
instant: {
|
||||
key: 'instant',
|
||||
label: `${t('menu.run')} (${t('menu.instant')})`,
|
||||
tooltip: t('menu.instantTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'instant'
|
||||
}
|
||||
},
|
||||
change: {
|
||||
key: 'change',
|
||||
label: `${t('menu.run')} (${t('menu.onChange')})`,
|
||||
tooltip: t('menu.onChangeTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'change'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const activeQueueModeMenuItem = computed(
|
||||
() => queueModeMenuItemLookup.value[queueMode.value]
|
||||
)
|
||||
const queueModeMenuItems = computed(() =>
|
||||
Object.values(queueModeMenuItemLookup.value)
|
||||
)
|
||||
|
||||
const executingPrompt = computed(() => !!queueCountStore.count.value)
|
||||
const hasPendingTasks = computed(
|
||||
() => queueCountStore.count.value > 1 || queueMode.value !== 'disabled'
|
||||
)
|
||||
|
||||
const commandStore = useCommandStore()
|
||||
const queuePrompt = async (e: Event) => {
|
||||
const commandId =
|
||||
'shiftKey' in e && e.shiftKey
|
||||
? 'Comfy.QueuePromptFront'
|
||||
: 'Comfy.QueuePrompt'
|
||||
await commandStore.execute(commandId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.comfyui-queue-button :deep(.p-splitbutton-dropdown) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
7
src/components/actionbar/ComfyRunButton/index.ts
Normal file
7
src/components/actionbar/ComfyRunButton/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
export default isCloud
|
||||
? defineAsyncComponent(() => import('./CloudRunButtonWrapper.vue'))
|
||||
: defineAsyncComponent(() => import('./ComfyQueueButton.vue'))
|
||||
Reference in New Issue
Block a user