Skip to content

Commit 83b0d04

Browse files
Merge branch 'rh-test' into bl-backport-something
2 parents 8c46a4b + 2383a38 commit 83b0d04

File tree

14 files changed

+317
-61
lines changed

14 files changed

+317
-61
lines changed

packages/design-system/src/css/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129
--button-surface: var(--color-white);
130130
--button-surface-contrast: var(--color-black);
131131

132+
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
133+
132134
--modal-card-button-surface: var(--color-smoke-300);
133135

134136
/* Code styling colors for help menu*/
@@ -180,6 +182,8 @@
180182
--button-active-surface: var(--color-charcoal-600);
181183
--button-icon: var(--color-smoke-800);
182184

185+
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
186+
183187
--modal-card-button-surface: var(--color-charcoal-300);
184188

185189
--dialog-surface: var(--color-neutral-700);
@@ -215,6 +219,7 @@
215219
--color-button-icon: var(--button-icon);
216220
--color-button-surface: var(--button-surface);
217221
--color-button-surface-contrast: var(--button-surface-contrast);
222+
--color-subscription-button-gradient: var(--subscription-button-gradient);
218223
--color-modal-card-button-surface: var(--modal-card-button-surface);
219224
--color-dialog-surface: var(--dialog-surface);
220225
--color-node-component-border: var(--node-component-border);

src/components/custom/widget/WorkflowTemplateSelectorDialog.vue

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@
380380
<script setup lang="ts">
381381
import { useAsyncState } from '@vueuse/core'
382382
import ProgressSpinner from 'primevue/progressspinner'
383-
import { computed, onBeforeUnmount, provide, ref, watch } from 'vue'
383+
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
384384
import { useI18n } from 'vue-i18n'
385385
386386
import IconButton from '@/components/button/IconButton.vue'
@@ -401,6 +401,8 @@ import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'
401401
import { useIntersectionObserver } from '@/composables/useIntersectionObserver'
402402
import { useLazyPagination } from '@/composables/useLazyPagination'
403403
import { useTemplateFiltering } from '@/composables/useTemplateFiltering'
404+
import { isCloud } from '@/platform/distribution/types'
405+
import { useTelemetry } from '@/platform/telemetry'
404406
import { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows'
405407
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
406408
import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
@@ -410,10 +412,34 @@ import { createGridStyle } from '@/utils/gridUtil'
410412
411413
const { t } = useI18n()
412414
413-
const { onClose } = defineProps<{
415+
const { onClose: originalOnClose } = defineProps<{
414416
onClose: () => void
415417
}>()
416418
419+
// Track session time for telemetry
420+
const sessionStartTime = ref<number>(0)
421+
const templateWasSelected = ref(false)
422+
423+
onMounted(() => {
424+
sessionStartTime.value = Date.now()
425+
})
426+
427+
// Wrap onClose to track session end
428+
const onClose = () => {
429+
if (isCloud) {
430+
const timeSpentSeconds = Math.floor(
431+
(Date.now() - sessionStartTime.value) / 1000
432+
)
433+
434+
useTelemetry()?.trackTemplateLibraryClosed({
435+
template_selected: templateWasSelected.value,
436+
time_spent_seconds: timeSpentSeconds
437+
})
438+
}
439+
440+
originalOnClose()
441+
}
442+
417443
provide(OnCloseKey, onClose)
418444
419445
// Workflow templates store and composable
@@ -698,6 +724,7 @@ const onLoadWorkflow = async (template: any) => {
698724
template.name,
699725
getEffectiveSourceModule(template)
700726
)
727+
templateWasSelected.value = true
701728
onClose()
702729
} finally {
703730
loadingTemplate.value = null

src/components/sidebar/SidebarTemplatesButton.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,18 @@
1212
<script setup lang="ts">
1313
import { computed } from 'vue'
1414
15+
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
1516
import { useSettingStore } from '@/platform/settings/settingStore'
16-
import { useCommandStore } from '@/stores/commandStore'
1717
1818
import SidebarIcon from './SidebarIcon.vue'
1919
2020
const settingStore = useSettingStore()
21-
const commandStore = useCommandStore()
2221
2322
const isSmall = computed(
2423
() => settingStore.get('Comfy.Sidebar.Size') === 'small'
2524
)
2625
2726
const openTemplates = () => {
28-
void commandStore.execute('Comfy.BrowseTemplates')
27+
useWorkflowTemplateSelectorDialog().show('sidebar')
2928
}
3029
</script>

src/components/topbar/CommandMenubar.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import { useI18n } from 'vue-i18n'
8080
8181
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
8282
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
83+
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
8384
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
8485
import { useSettingStore } from '@/platform/settings/settingStore'
8586
import { useColorPaletteService } from '@/services/colorPaletteService'
@@ -168,7 +169,7 @@ const extraMenuItems = computed(() => [
168169
key: 'browse-templates',
169170
label: t('menuLabels.Browse Templates'),
170171
icon: 'icon-[comfy--template]',
171-
command: () => commandStore.execute('Comfy.BrowseTemplates')
172+
command: () => useWorkflowTemplateSelectorDialog().show('menu')
172173
},
173174
{
174175
key: 'settings',

src/components/topbar/CurrentUserPopover.test.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ vi.mock('@/stores/firebaseAuthStore', () => ({
7979
}))
8080

8181
// Mock the useSubscription composable
82+
const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
8283
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
8384
useSubscription: vi.fn(() => ({
84-
isActiveSubscription: vi.fn().mockReturnValue(true)
85+
isActiveSubscription: { value: true },
86+
fetchStatus: mockFetchStatus
8587
}))
8688
}))
8789

@@ -105,6 +107,15 @@ vi.mock('@/components/common/UserCredit.vue', () => ({
105107
}
106108
}))
107109

110+
vi.mock('@/platform/cloud/subscription/components/SubscribeButton.vue', () => ({
111+
default: {
112+
name: 'SubscribeButtonMock',
113+
render() {
114+
return h('div', 'Subscribe Button')
115+
}
116+
}
117+
}))
118+
108119
describe('CurrentUserPopover', () => {
109120
beforeEach(() => {
110121
vi.clearAllMocks()
@@ -137,9 +148,9 @@ describe('CurrentUserPopover', () => {
137148
it('renders logout button with correct props', () => {
138149
const wrapper = mountComponent()
139150

140-
// Find all buttons and get the logout button (second one)
151+
// Find all buttons and get the logout button (last button)
141152
const buttons = wrapper.findAllComponents(Button)
142-
const logoutButton = buttons[1]
153+
const logoutButton = buttons[4]
143154

144155
// Check that logout button has correct props
145156
expect(logoutButton.props('label')).toBe('Log Out')
@@ -149,9 +160,9 @@ describe('CurrentUserPopover', () => {
149160
it('opens user settings and emits close event when settings button is clicked', async () => {
150161
const wrapper = mountComponent()
151162

152-
// Find all buttons and get the settings button (first one)
163+
// Find all buttons and get the settings button (third button)
153164
const buttons = wrapper.findAllComponents(Button)
154-
const settingsButton = buttons[0]
165+
const settingsButton = buttons[2]
155166

156167
// Click the settings button
157168
await settingsButton.trigger('click')
@@ -167,9 +178,9 @@ describe('CurrentUserPopover', () => {
167178
it('calls logout function and emits close event when logout button is clicked', async () => {
168179
const wrapper = mountComponent()
169180

170-
// Find all buttons and get the logout button (second one)
181+
// Find all buttons and get the logout button (last button)
171182
const buttons = wrapper.findAllComponents(Button)
172-
const logoutButton = buttons[1]
183+
const logoutButton = buttons[4]
173184

174185
// Click the logout button
175186
await logoutButton.trigger('click')
@@ -185,16 +196,16 @@ describe('CurrentUserPopover', () => {
185196
it('opens API pricing docs and emits close event when API pricing button is clicked', async () => {
186197
const wrapper = mountComponent()
187198

188-
// Find all buttons and get the API pricing button (third one now)
199+
// Find all buttons and get the Partner Nodes info button (first one)
189200
const buttons = wrapper.findAllComponents(Button)
190-
const apiPricingButton = buttons[2]
201+
const partnerNodesButton = buttons[0]
191202

192-
// Click the API pricing button
193-
await apiPricingButton.trigger('click')
203+
// Click the Partner Nodes button
204+
await partnerNodesButton.trigger('click')
194205

195206
// Verify window.open was called with the correct URL
196207
expect(window.open).toHaveBeenCalledWith(
197-
'https://docs.comfy.org/tutorials/api-nodes/pricing',
208+
'https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes',
198209
'_blank'
199210
)
200211

@@ -206,9 +217,9 @@ describe('CurrentUserPopover', () => {
206217
it('opens top-up dialog and emits close event when top-up button is clicked', async () => {
207218
const wrapper = mountComponent()
208219

209-
// Find all buttons and get the top-up button (last one)
220+
// Find all buttons and get the top-up button (second one)
210221
const buttons = wrapper.findAllComponents(Button)
211-
const topUpButton = buttons[buttons.length - 1]
222+
const topUpButton = buttons[1]
212223

213224
// Click the top-up button
214225
await topUpButton.trigger('click')

src/components/topbar/CurrentUserPopover.vue

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,38 @@
2323
</div>
2424
</div>
2525

26+
<div v-if="isActiveSubscription" class="flex items-center justify-between">
27+
<div class="flex flex-col gap-1">
28+
<UserCredit text-class="text-2xl" />
29+
<Button
30+
:label="$t('subscription.partnerNodesCredits')"
31+
severity="secondary"
32+
text
33+
size="small"
34+
class="pl-6 p-0 h-auto justify-start"
35+
:pt="{
36+
root: {
37+
class: 'hover:bg-transparent active:bg-transparent'
38+
}
39+
}"
40+
@click="handleOpenPartnerNodesInfo"
41+
/>
42+
</div>
43+
<Button
44+
:label="$t('credits.topUp.topUp')"
45+
severity="secondary"
46+
size="small"
47+
@click="handleTopUp"
48+
/>
49+
</div>
50+
<SubscribeButton
51+
v-else
52+
:label="$t('subscription.subscribeToComfyCloud')"
53+
size="small"
54+
variant="gradient"
55+
@subscribed="handleSubscribed"
56+
/>
57+
2658
<Divider class="my-2" />
2759

2860
<Button
@@ -35,45 +67,28 @@
3567
@click="handleOpenUserSettings"
3668
/>
3769

38-
<Divider class="my-2" />
39-
4070
<Button
71+
v-if="isActiveSubscription"
4172
class="justify-start"
42-
:label="$t('auth.signOut.signOut')"
43-
icon="pi pi-sign-out"
73+
:label="$t(planSettingsLabel)"
74+
icon="pi pi-receipt"
4475
text
4576
fluid
4677
severity="secondary"
47-
@click="handleLogout"
78+
@click="handleOpenPlanAndCreditsSettings"
4879
/>
4980

5081
<Divider class="my-2" />
5182

5283
<Button
5384
class="justify-start"
54-
:label="$t('credits.apiPricing')"
55-
icon="pi pi-external-link"
85+
:label="$t('auth.signOut.signOut')"
86+
icon="pi pi-sign-out"
5687
text
5788
fluid
5889
severity="secondary"
59-
@click="handleOpenApiPricing"
90+
@click="handleLogout"
6091
/>
61-
62-
<Divider class="my-2" />
63-
64-
<div class="flex w-full flex-col gap-2 p-2">
65-
<div class="text-sm text-muted">
66-
{{ $t('credits.yourCreditBalance') }}
67-
</div>
68-
<div class="flex items-center justify-between">
69-
<UserCredit text-class="text-2xl" />
70-
<Button
71-
v-if="isActiveSubscription"
72-
:label="$t('credits.topUp.topUp')"
73-
@click="handleTopUp"
74-
/>
75-
</div>
76-
</div>
7792
</div>
7893
</template>
7994

@@ -86,37 +101,60 @@ import UserAvatar from '@/components/common/UserAvatar.vue'
86101
import UserCredit from '@/components/common/UserCredit.vue'
87102
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
88103
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
104+
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
89105
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
106+
import { isCloud } from '@/platform/distribution/types'
90107
import { useDialogService } from '@/services/dialogService'
91108
92109
const emit = defineEmits<{
93110
close: []
94111
}>()
95112
113+
const planSettingsLabel = isCloud
114+
? 'settingsCategories.PlanCredits'
115+
: 'settingsCategories.Credits'
116+
96117
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
97118
useCurrentUser()
98119
const authActions = useFirebaseAuthActions()
99120
const dialogService = useDialogService()
100-
const { isActiveSubscription } = useSubscription()
121+
const { isActiveSubscription, fetchStatus } = useSubscription()
101122
102123
const handleOpenUserSettings = () => {
103124
dialogService.showSettingsDialog('user')
104125
emit('close')
105126
}
106127
128+
const handleOpenPlanAndCreditsSettings = () => {
129+
if (isCloud) {
130+
dialogService.showSettingsDialog('subscription')
131+
} else {
132+
dialogService.showSettingsDialog('credits')
133+
}
134+
135+
emit('close')
136+
}
137+
107138
const handleTopUp = () => {
108139
dialogService.showTopUpCreditsDialog()
109140
emit('close')
110141
}
111142
143+
const handleOpenPartnerNodesInfo = () => {
144+
window.open(
145+
'https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes',
146+
'_blank'
147+
)
148+
emit('close')
149+
}
150+
112151
const handleLogout = async () => {
113152
await handleSignOut()
114153
emit('close')
115154
}
116155
117-
const handleOpenApiPricing = () => {
118-
window.open('https://docs.comfy.org/tutorials/api-nodes/pricing', '_blank')
119-
emit('close')
156+
const handleSubscribed = async () => {
157+
await fetchStatus()
120158
}
121159
122160
onMounted(() => {

src/composables/useWorkflowTemplateSelectorDialog.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import WorkflowTemplateSelectorDialog from '@/components/custom/widget/WorkflowTemplateSelectorDialog.vue'
2+
import { useTelemetry } from '@/platform/telemetry'
23
import { useDialogService } from '@/services/dialogService'
34
import { useDialogStore } from '@/stores/dialogStore'
45

@@ -12,7 +13,9 @@ export const useWorkflowTemplateSelectorDialog = () => {
1213
dialogStore.closeDialog({ key: DIALOG_KEY })
1314
}
1415

15-
function show() {
16+
function show(source: 'sidebar' | 'menu' | 'command' = 'command') {
17+
useTelemetry()?.trackTemplateLibraryOpened({ source })
18+
1619
dialogService.showLayoutDialog({
1720
key: DIALOG_KEY,
1821
component: WorkflowTemplateSelectorDialog,

0 commit comments

Comments
 (0)