Files
ComfyUI_frontend/src/components/topbar/TopMenubar.vue
Johnpaul Chiwetelu 7caad10e93 Badge for cloud environment (#6048)
This pull request introduces a new system for displaying
environment-specific badges in the application's top bar, with a focus
on supporting a "Comfy Cloud" badge in production environments. The
changes include new badge types, extension support, UI components, and
environment detection logic to ensure badges are only shown in
appropriate contexts.

**Topbar Badge System**

* Added a new `TopbarBadge` type and support for topbar badges in the
`ComfyExtension` interface to allow extensions to specify badges for the
top bar.
[[1]](diffhunk://#diff-c29886a1b0c982c6fff3545af0ca8ec269876c2cf3948f867d08c14032c04d66R24-R31)
[[2]](diffhunk://#diff-c29886a1b0c982c6fff3545af0ca8ec269876c2cf3948f867d08c14032c04d66R85-R88)
* Created a Pinia store `topbarBadgeStore` to aggregate topbar badges
from all registered extensions for display.

**UI Integration**

* Added a new `TopbarBadges.vue` component to render topbar badges and
integrated it into the top menu bar UI.
[[1]](diffhunk://#diff-6f460b1398fd033a2059daca1f991c74ce572cef86046a3726d1b1a70a3a4325R1-R32)
[[2]](diffhunk://#diff-b7d7bf1028f09fb907c09edf27631214d005c93b80eaff7cf15cfd53671b1e8aL5-R14)
* Updated CSS variables and menu styling to support the new badge
visuals.
[[1]](diffhunk://#diff-71b6b57a56095b04e47c797a5016149b76b27971cab04b93f033f1f846e0f5a0R88-R89)
[[2]](diffhunk://#diff-b7d7bf1028f09fb907c09edf27631214d005c93b80eaff7cf15cfd53671b1e8aL5-R14)

**Environment Detection and Extension Registration**

* Added a runtime environment detection utility to determine if the app
is running in production or staging, replacing the previous build-time
constant approach.
* Registered a new `cloudBadge` extension that conditionally adds a
"Comfy Cloud" badge with a "BETA" label when running in production.
[[1]](diffhunk://#diff-b7818ca9daae2411d56695777160b8132507f2a3ff4f700d2510453c8833ca75R1-R15)
[[2]](diffhunk://#diff-236993d9e4213efe96d267c75c3292d32b93aa4dd6c3318d26a397e0ae56bc87R2)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6048-Badge-for-cloud-environment-28c6d73d365081188050ece527c3c8f3)
by [Unito](https://www.unito.io)

<img width="996" height="897" alt="Screenshot 2025-10-14 at 20 02 40"
src="https://github.com/user-attachments/assets/5a3258c5-87fc-46ae-ad23-7669696cb8b6"
/>
2025-10-15 00:44:32 +01:00

160 lines
4.3 KiB
Vue

<template>
<div>
<div
v-show="showTopMenu && workflowTabsPosition === 'Topbar'"
class="z-1001 flex h-9.5 w-full content-end"
style="background: var(--border-color)"
>
<WorkflowTabs />
<CloudBetaBadge v-if="isCloud" />
</div>
<div
v-show="showTopMenu"
ref="topMenuRef"
class="comfyui-menu flex items-center bg-gray-100"
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
>
<CommandMenubar />
<div class="app-drag h-full min-w-0 grow"></div>
<div
ref="menuRight"
class="comfyui-menu-right flex-shrink-1 overflow-auto"
/>
<Actionbar />
<CurrentUserButton class="shrink-0" />
</div>
<!-- Virtual top menu for native window (drag handle) -->
<div
v-show="isNativeWindow() && !showTopMenu"
class="app-drag fixed top-0 left-0 h-(--comfy-topbar-height) w-full"
/>
</div>
</template>
<script setup lang="ts">
import { useEventBus } from '@vueuse/core'
import { computed, onMounted, provide, ref } from 'vue'
import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
import CommandMenubar from '@/components/topbar/CommandMenubar.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI, isElectron, isNativeWindow } from '@/utils/envUtil'
import CloudBetaBadge from './CloudBetaBadge.vue'
const workspaceState = useWorkspaceStore()
const settingStore = useSettingStore()
const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu'))
const betaMenuEnabled = computed(() => menuSetting.value !== 'Disabled')
const showTopMenu = computed(
() => betaMenuEnabled.value && !workspaceState.focusMode
)
const workflowTabsPosition = computed(() =>
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')
)
const menuRight = ref<HTMLDivElement | null>(null)
// Menu-right holds legacy topbar elements attached by custom scripts
onMounted(() => {
if (menuRight.value) {
app.menu.element.style.width = 'fit-content'
menuRight.value.appendChild(app.menu.element)
}
})
const topMenuRef = ref<HTMLDivElement | null>(null)
provide('topMenuRef', topMenuRef)
const eventBus = useEventBus<string>('topMenu')
const isDropZone = ref(false)
const isDroppable = ref(false)
eventBus.on((event: string, payload: any) => {
if (event === 'updateHighlight') {
isDropZone.value = payload.isDragging
isDroppable.value = payload.isOverlapping && payload.isDragging
}
})
onMounted(() => {
if (isElectron()) {
electronAPI().changeTheme({
height: topMenuRef.value?.getBoundingClientRect().height ?? 0
})
}
})
</script>
<style scoped>
.comfyui-menu {
width: 100vw;
height: var(--comfy-topbar-height);
background: var(--comfy-menu-bg);
color: var(--fg-color);
box-shadow: var(--bar-shadow);
font-family: Arial, Helvetica, sans-serif;
font-size: 0.8em;
box-sizing: border-box;
z-index: 1000;
order: 0;
grid-column: 1/-1;
}
.comfyui-menu.dropzone {
background: var(--p-highlight-background);
}
.comfyui-menu.dropzone-active {
background: var(--p-highlight-background-focus);
}
:deep(.p-menubar-item-label) {
line-height: revert;
}
.comfyui-logo {
user-select: none;
cursor: default;
filter: invert(0);
}
.dark-theme .comfyui-logo {
filter: invert(1);
}
.comfyui-menu-button-hide {
background-color: var(--comfy-menu-secondary-bg);
border-left: 1px solid var(--border-color);
}
</style>
<style>
.comfyui-menu-right::-webkit-scrollbar {
max-height: 5px;
}
.comfyui-menu-right:hover::-webkit-scrollbar {
cursor: grab;
}
.comfyui-menu-right::-webkit-scrollbar-track {
background: color-mix(in srgb, var(--border-color) 60%, transparent);
}
.comfyui-menu-right:hover::-webkit-scrollbar-track {
background: color-mix(in srgb, var(--border-color) 80%, transparent);
}
.comfyui-menu-right::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--fg-color) 30%, transparent);
}
.comfyui-menu-right::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--fg-color) 80%, transparent);
}
</style>