Skip to content
2 changes: 2 additions & 0 deletions src/components/TopMenuSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<div
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
>
<ActionBarButtons />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (non-blocking): Is it worth to do v-if on condition !!button.length?

<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
ref="legacyCommandsContainerRef"
Expand All @@ -24,6 +25,7 @@ import { onMounted, ref } from 'vue'

import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
Expand Down
29 changes: 29 additions & 0 deletions src/components/topbar/ActionBarButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<div class="flex h-full shrink-0 items-center gap-1">
<Button
v-for="(button, index) in actionBarButtonStore.buttons"
:key="index"
v-tooltip.bottom="button.tooltip"
:label="button.label"
:aria-label="button.tooltip || button.label"
:class="button.class"
text
rounded
severity="secondary"
class="h-7"
@click="button.onClick"
>
<template #icon>
<i :class="button.icon" />
</template>
</Button>
</div>
</template>

<script lang="ts" setup>
import Button from 'primevue/button'

import { useActionBarButtonStore } from '@/stores/actionBarButtonStore'

const actionBarButtonStore = useActionBarButtonStore()
</script>
23 changes: 23 additions & 0 deletions src/extensions/core/cloudFeedbackTopbarButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { t } from '@/i18n'
import { useExtensionService } from '@/services/extensionService'
import type { ActionBarButton } from '@/types/comfy'

// Zendesk feedback URL - update this with the actual URL
const ZENDESK_FEEDBACK_URL =
'https://support.comfy.org/hc/en-us/requests/new?ticket_form_id=43066738713236'

const buttons: ActionBarButton[] = [
{
icon: 'icon-[lucide--message-circle-question-mark]',
label: t('actionbar.feedback'),
tooltip: t('actionbar.feedbackTooltip'),
onClick: () => {
window.open(ZENDESK_FEEDBACK_URL, '_blank', 'noopener,noreferrer')
}
}
]

useExtensionService().registerExtension({
name: 'Comfy.Cloud.FeedbackButton',
actionBarButtons: buttons
})
1 change: 1 addition & 0 deletions src/extensions/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ if (isCloud) {
await import('./cloudRemoteConfig')
await import('./cloudBadges')
await import('./cloudSessionCookie')
await import('./cloudFeedbackTopbarButton')

if (window.__CONFIG__?.subscription_required) {
await import('./cloudSubscription')
Expand Down
4 changes: 3 additions & 1 deletion src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2282,7 +2282,9 @@
}
},
"actionbar": {
"dockToTop": "Dock to top"
"dockToTop": "Dock to top",
"feedback": "Feedback",
"feedbackTooltip": "Feedback"
},
"desktopDialogs": {
"": {
Expand Down
18 changes: 18 additions & 0 deletions src/stores/actionBarButtonStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineStore } from 'pinia'
import { computed } from 'vue'

import type { ActionBarButton } from '@/types/comfy'

import { useExtensionStore } from './extensionStore'

export const useActionBarButtonStore = defineStore('actionBarButton', () => {
const extensionStore = useExtensionStore()

const buttons = computed<ActionBarButton[]>(() =>
extensionStore.extensions.flatMap((e) => e.actionBarButtons ?? [])
)

return {
buttons
}
})
30 changes: 30 additions & 0 deletions src/types/comfy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ export interface TopbarBadge {
tooltip?: string
}

/*
* Action bar button definition: add buttons to the action bar
*/
export interface ActionBarButton {
/**
* Icon class to display (e.g., "icon-[lucide--message-circle-question-mark]")
*/
icon: string
/**
* Optional label text to display next to the icon
*/
label?: string
/**
* Optional tooltip text to show on hover
*/
tooltip?: string
/**
* Optional CSS classes to apply to the button
*/
class?: string
/**
* Click handler for the button
*/
onClick: () => void
}

export type MissingNodeType =
| string
// Primarily used by group nodes.
Expand Down Expand Up @@ -102,6 +128,10 @@ export interface ComfyExtension {
* Badges to add to the top bar
*/
topbarBadges?: TopbarBadge[]
/**
* Buttons to add to the action bar
*/
actionBarButtons?: ActionBarButton[]
/**
* Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added
* @param app The ComfyUI app instance
Expand Down