Integrated tab bar UI elements (#7853)

## Summary

The current help / feedback is often overlooked by users, this adds a
setting that makes it more visible moving it up into the tab bar and
moves the user login/profile button out of the "action bar" into the tab
bar.

## Changes
- Add 'Comfy.UI.TabBarLayout' setting with Default/Integrated options
- Move Help & User controls to tab bar when Integrated mode is enabled
- Extract help center logic into shared useHelpCenter composable

## Screenshots (if applicable)

<img width="515" height="540" alt="image"
src="https://github.com/user-attachments/assets/c9e6057f-4fb1-4da6-b25d-9df4b19be31a"
/>
<img width="835" height="268" alt="image"
src="https://github.com/user-attachments/assets/24afc0e8-97eb-45cf-af86-15a9b464e9a8"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7853-Integrated-tab-bar-UI-elements-2df6d73d365081b1beb8f7c641c2fa43)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
pythongosssss
2026-01-11 07:11:50 +00:00
committed by GitHub
parent 7b274b74f1
commit 97ca9f489e
14 changed files with 364 additions and 199 deletions

View File

@@ -59,8 +59,11 @@
{{ queuedCount }} {{ queuedCount }}
</span> </span>
</Button> </Button>
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" /> <CurrentUserButton
<LoginButton v-else-if="isDesktop" /> v-if="isLoggedIn && !isIntegratedTabBar"
class="shrink-0"
/>
<LoginButton v-else-if="isDesktop && !isIntegratedTabBar" />
<Button <Button
v-if="!isRightSidePanelOpen" v-if="!isRightSidePanelOpen"
v-tooltip.bottom="rightSidePanelTooltipConfig" v-tooltip.bottom="rightSidePanelTooltipConfig"
@@ -96,6 +99,7 @@ import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useErrorHandling } from '@/composables/useErrorHandling' import { useErrorHandling } from '@/composables/useErrorHandling'
import { buildTooltipConfig } from '@/composables/useTooltipConfig' import { buildTooltipConfig } from '@/composables/useTooltipConfig'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useReleaseStore } from '@/platform/updates/common/releaseStore' import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { app } from '@/scripts/app' import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore' import { useCommandStore } from '@/stores/commandStore'
@@ -107,6 +111,7 @@ import { useConflictAcknowledgment } from '@/workbench/extensions/manager/compos
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState' import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes' import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const settingStore = useSettingStore()
const workspaceStore = useWorkspaceStore() const workspaceStore = useWorkspaceStore()
const rightSidePanelStore = useRightSidePanelStore() const rightSidePanelStore = useRightSidePanelStore()
const managerState = useManagerState() const managerState = useManagerState()
@@ -124,6 +129,9 @@ const { shouldShowRedDot: shouldShowConflictRedDot } =
useConflictAcknowledgment() useConflictAcknowledgment()
const isTopMenuHovered = ref(false) const isTopMenuHovered = ref(false)
const queuedCount = computed(() => queueStore.pendingTasks.length) const queuedCount = computed(() => queueStore.pendingTasks.length)
const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
const queueHistoryTooltipConfig = computed(() => const queueHistoryTooltipConfig = computed(() =>
buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory')) buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))
) )

View File

@@ -0,0 +1,134 @@
<template>
<!-- Help Center Popup positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<div
v-if="isHelpCenterVisible"
class="help-center-popup"
:class="{
'sidebar-left':
triggerLocation === 'sidebar' && sidebarLocation === 'left',
'sidebar-right':
triggerLocation === 'sidebar' && sidebarLocation === 'right',
'topbar-right': triggerLocation === 'topbar',
'small-sidebar': isSmall
}"
>
<HelpCenterMenuContent @close="closeHelpCenter" />
</div>
</Teleport>
<!-- Release Notification Toast positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<ReleaseNotificationToast
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': isSmall
}"
/>
</Teleport>
<!-- WhatsNew Popup positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<WhatsNewPopup
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': isSmall
}"
@whats-new-dismissed="handleWhatsNewDismissed"
/>
</Teleport>
<!-- Backdrop to close popup when clicking outside -->
<Teleport to="body">
<div
v-if="isHelpCenterVisible"
class="help-center-backdrop"
@click="closeHelpCenter"
/>
</Teleport>
</template>
<script setup lang="ts">
import { useHelpCenter } from '@/composables/useHelpCenter'
import ReleaseNotificationToast from '@/platform/updates/components/ReleaseNotificationToast.vue'
import WhatsNewPopup from '@/platform/updates/components/WhatsNewPopup.vue'
import HelpCenterMenuContent from './HelpCenterMenuContent.vue'
const { isSmall = false } = defineProps<{
isSmall?: boolean
}>()
const {
isHelpCenterVisible,
triggerLocation,
sidebarLocation,
closeHelpCenter,
handleWhatsNewDismissed
} = useHelpCenter()
</script>
<style scoped>
.help-center-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background: transparent;
}
.help-center-popup {
position: absolute;
bottom: 1rem;
z-index: 10000;
animation: slideInUp 0.2s ease-out;
pointer-events: auto;
}
.help-center-popup.sidebar-left {
left: 1rem;
}
.help-center-popup.sidebar-left.small-sidebar {
left: 1rem;
}
.help-center-popup.sidebar-right {
right: 1rem;
}
.help-center-popup.topbar-right {
top: 2rem;
right: 1rem;
bottom: auto;
animation: slideInDown 0.2s ease-out;
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -40,12 +40,13 @@
v-if="userStore.isMultiUserServer" v-if="userStore.isMultiUserServer"
:is-small="isSmall" :is-small="isSmall"
/> />
<SidebarHelpCenterIcon :is-small="isSmall" /> <SidebarHelpCenterIcon v-if="!isIntegratedTabBar" :is-small="isSmall" />
<SidebarBottomPanelToggleButton :is-small="isSmall" /> <SidebarBottomPanelToggleButton :is-small="isSmall" />
<SidebarShortcutsToggleButton :is-small="isSmall" /> <SidebarShortcutsToggleButton :is-small="isSmall" />
<SidebarSettingsButton :is-small="isSmall" /> <SidebarSettingsButton :is-small="isSmall" />
</div> </div>
</div> </div>
<HelpCenterPopups :is-small="isSmall" />
</nav> </nav>
</template> </template>
@@ -54,6 +55,7 @@ import { useResizeObserver } from '@vueuse/core'
import { debounce } from 'es-toolkit/compat' import { debounce } from 'es-toolkit/compat'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'
import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue' import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue' import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue' import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
@@ -89,6 +91,9 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location') settingStore.get('Comfy.Sidebar.Location')
) )
const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style')) const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))
const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
const isConnected = computed( const isConnected = computed(
() => () =>
selectedTab.value || selectedTab.value ||

View File

@@ -1,204 +1,28 @@
<template> <template>
<div> <SidebarIcon
<SidebarIcon icon="pi pi-question-circle"
icon="pi pi-question-circle" class="comfy-help-center-btn"
class="comfy-help-center-btn" :label="$t('menu.help')"
:label="$t('menu.help')" :tooltip="$t('sideToolbar.helpCenter')"
:tooltip="$t('sideToolbar.helpCenter')" :icon-badge="shouldShowRedDot ? '' : ''"
:icon-badge="shouldShowRedDot ? '' : ''" :is-small="isSmall"
:is-small="isSmall" @click="toggleHelpCenter"
@click="toggleHelpCenter" />
/>
<!-- Help Center Popup positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<div
v-if="isHelpCenterVisible"
class="help-center-popup"
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': isSmall
}"
>
<HelpCenterMenuContent @close="closeHelpCenter" />
</div>
</Teleport>
<!-- Release Notification Toast positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<ReleaseNotificationToast
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': isSmall
}"
/>
</Teleport>
<!-- WhatsNew Popup positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<WhatsNewPopup
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': isSmall
}"
@whats-new-dismissed="handleWhatsNewDismissed"
/>
</Teleport>
<!-- Backdrop to close popup when clicking outside -->
<Teleport to="body">
<div
v-if="isHelpCenterVisible"
class="help-center-backdrop"
@click="closeHelpCenter"
/>
</Teleport>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from 'pinia' import { useHelpCenter } from '@/composables/useHelpCenter'
import { computed, onMounted, toRefs } from 'vue'
import HelpCenterMenuContent from '@/components/helpcenter/HelpCenterMenuContent.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import ReleaseNotificationToast from '@/platform/updates/components/ReleaseNotificationToast.vue'
import WhatsNewPopup from '@/platform/updates/components/WhatsNewPopup.vue'
import { useDialogService } from '@/services/dialogService'
import { useHelpCenterStore } from '@/stores/helpCenterStore'
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
import SidebarIcon from './SidebarIcon.vue' import SidebarIcon from './SidebarIcon.vue'
const settingStore = useSettingStore() defineProps<{
const releaseStore = useReleaseStore()
const helpCenterStore = useHelpCenterStore()
const { isVisible: isHelpCenterVisible } = storeToRefs(helpCenterStore)
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
const conflictDetection = useConflictDetection()
const { showNodeConflictDialog } = useDialogService()
// Use conflict acknowledgment state from composable - call only once
const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } =
useConflictAcknowledgment()
const props = defineProps<{
isSmall: boolean isSmall: boolean
}>() }>()
const { isSmall } = toRefs(props)
// Use either release red dot or conflict red dot const { shouldShowRedDot, toggleHelpCenter } = useHelpCenter()
const shouldShowRedDot = computed((): boolean => {
const releaseRedDot = showReleaseRedDot.value
return releaseRedDot || shouldShowConflictRedDot.value
})
const sidebarLocation = computed(() =>
settingStore.get('Comfy.Sidebar.Location')
)
/**
* Toggle Help Center and track UI button click.
*/
const toggleHelpCenter = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_help_center_toggled'
})
helpCenterStore.toggle()
}
const closeHelpCenter = () => {
helpCenterStore.hide()
}
/**
* Handle What's New popup dismissal
* Check if conflict modal should be shown after ComfyUI update
*/
const handleWhatsNewDismissed = async () => {
try {
// Check if conflict modal should be shown after update
const shouldShow =
await conflictDetection.shouldShowConflictModalAfterUpdate()
if (shouldShow) {
showConflictModal()
}
} catch (error) {
console.error('[HelpCenter] Error checking conflict modal:', error)
}
}
/**
* Show the node conflict dialog with current conflict data
*/
const showConflictModal = () => {
showNodeConflictDialog({
showAfterWhatsNew: true,
dialogComponentProps: {
onClose: () => {
markConflictsAsSeen()
}
}
})
}
// Initialize release store on mount
onMounted(async () => {
// Initialize release store to fetch releases for toast and popup
await releaseStore.initialize()
})
</script> </script>
<style scoped> <style scoped>
.help-center-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background: transparent;
}
.help-center-popup {
position: absolute;
bottom: 1rem;
z-index: 10000;
animation: slideInUp 0.2s ease-out;
pointer-events: auto;
}
.help-center-popup.sidebar-left {
left: 1rem;
}
.help-center-popup.sidebar-left.small-sidebar {
left: 1rem;
}
.help-center-popup.sidebar-right {
right: 1rem;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
:deep(.p-badge) { :deep(.p-badge) {
background: #ff3b30; background: #ff3b30;
color: #ff3b30; color: #ff3b30;

View File

@@ -9,11 +9,16 @@
@click="popover?.toggle($event)" @click="popover?.toggle($event)"
> >
<div <div
class="flex items-center gap-1 rounded-full hover:bg-interface-button-hover-surface" :class="
cn(
'flex items-center gap-1 rounded-full hover:bg-interface-button-hover-surface justify-center',
compact && 'size-full '
)
"
> >
<UserAvatar :photo-url="photoURL" /> <UserAvatar :photo-url="photoURL" :class="compact && 'size-full'" />
<i class="icon-[lucide--chevron-down] size-3 px-1" /> <i v-if="showArrow" class="icon-[lucide--chevron-down] size-3 px-1" />
</div> </div>
</Button> </Button>
@@ -38,9 +43,15 @@ import { computed, ref } from 'vue'
import UserAvatar from '@/components/common/UserAvatar.vue' import UserAvatar from '@/components/common/UserAvatar.vue'
import Button from '@/components/ui/button/Button.vue' import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { cn } from '@/utils/tailwindUtil'
import CurrentUserPopover from './CurrentUserPopover.vue' import CurrentUserPopover from './CurrentUserPopover.vue'
const { showArrow = true, compact = false } = defineProps<{
showArrow?: boolean
compact?: boolean
}>()
const { isLoggedIn, userPhotoUrl } = useCurrentUser() const { isLoggedIn, userPhotoUrl } = useCurrentUser()
const popover = ref<InstanceType<typeof Popover> | null>(null) const popover = ref<InstanceType<typeof Popover> | null>(null)

View File

@@ -1,15 +1,19 @@
<template> <template>
<Button <Button
v-if="!isLoggedIn" v-if="!isLoggedIn"
variant="secondary" variant="textonly"
size="icon" size="icon"
class="rounded-full bg-secondary-background text-base-foreground hover:bg-secondary-background-hover" :class="cn('group rounded-full text-base-foreground p-0', className)"
:aria-label="t('g.login')" :aria-label="t('g.login')"
@click="handleSignIn()" @click="handleSignIn()"
@mouseenter="showPopover" @mouseenter="showPopover"
@mouseleave="hidePopover" @mouseleave="hidePopover"
> >
<i class="icon-[lucide--user] size-4" /> <span
class="flex size-full items-center justify-center rounded-full bg-secondary-background transition-colors group-hover:bg-transparent"
>
<i class="icon-[lucide--user] size-4" />
</span>
</Button> </Button>
<Popover <Popover
ref="popoverRef" ref="popoverRef"
@@ -31,12 +35,18 @@
<script setup lang="ts"> <script setup lang="ts">
import Popover from 'primevue/popover' import Popover from 'primevue/popover'
import type { HTMLAttributes } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue' import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useExternalLink } from '@/composables/useExternalLink' import { useExternalLink } from '@/composables/useExternalLink'
import { t } from '@/i18n' import { t } from '@/i18n'
import { cn } from '@/utils/tailwindUtil'
const { class: className } = defineProps<{
class?: HTMLAttributes['class']
}>()
const { isLoggedIn, handleSignIn } = useCurrentUser() const { isLoggedIn, handleSignIn } = useCurrentUser()
const { buildDocsUrl } = useExternalLink() const { buildDocsUrl } = useExternalLink()

View File

@@ -0,0 +1,21 @@
<template>
<Button
class="comfy-help-center-btn relative text-base-foreground"
variant="textonly"
@click="toggleHelpCenter"
>
{{ $t('menu.helpAndFeedback') }}
<i class="icon-[lucide--circle-help] ml-0.5" />
<span
v-if="shouldShowRedDot"
class="absolute top-[7px] right-[7px] size-1.5 rounded-full bg-[#ff3b30]"
/>
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import { useHelpCenter } from '@/composables/useHelpCenter'
const { shouldShowRedDot, toggleHelpCenter } = useHelpCenter('topbar')
</script>

View File

@@ -67,6 +67,19 @@
> >
<i class="pi pi-plus" /> <i class="pi pi-plus" />
</Button> </Button>
<div
v-if="isIntegratedTabBar"
class="ml-auto flex shrink-0 items-center gap-2 px-2"
>
<TopMenuHelpButton />
<CurrentUserButton
v-if="isLoggedIn"
:show-arrow="false"
compact
class="shrink-0 p-1"
/>
<LoginButton v-else-if="isDesktop" class="p-1" />
</div>
<ContextMenu ref="menu" :model="contextMenuItems" /> <ContextMenu ref="menu" :model="contextMenuItems" />
<div v-if="isDesktop" class="window-actions-spacer app-drag shrink-0" /> <div v-if="isDesktop" class="window-actions-spacer app-drag shrink-0" />
</div> </div>
@@ -81,9 +94,14 @@ import { computed, nextTick, onUpdated, ref, watch } from 'vue'
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import TopMenuHelpButton from '@/components/topbar/TopMenuHelpButton.vue'
import WorkflowTab from '@/components/topbar/WorkflowTab.vue' import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
import Button from '@/components/ui/button/Button.vue' import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver' import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService' import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore' import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import { import {
@@ -107,10 +125,16 @@ const props = defineProps<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const settingStore = useSettingStore()
const workspaceStore = useWorkspaceStore() const workspaceStore = useWorkspaceStore()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const workflowBookmarkStore = useWorkflowBookmarkStore() const workflowBookmarkStore = useWorkflowBookmarkStore()
const workflowService = useWorkflowService() const workflowService = useWorkflowService()
const { isLoggedIn } = useCurrentUser()
const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
const rightClickedTab = ref<WorkflowOption | undefined>() const rightClickedTab = ref<WorkflowOption | undefined>()
const menu = ref() const menu = ref()

View File

@@ -0,0 +1,100 @@
import { storeToRefs } from 'pinia'
import { computed, onMounted } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { useDialogService } from '@/services/dialogService'
import { useHelpCenterStore } from '@/stores/helpCenterStore'
import type { HelpCenterTriggerLocation } from '@/stores/helpCenterStore'
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
export function useHelpCenter(
triggerFrom: HelpCenterTriggerLocation = 'sidebar'
) {
const settingStore = useSettingStore()
const releaseStore = useReleaseStore()
const helpCenterStore = useHelpCenterStore()
const { isVisible: isHelpCenterVisible, triggerLocation } =
storeToRefs(helpCenterStore)
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
const conflictDetection = useConflictDetection()
const { showNodeConflictDialog } = useDialogService()
// Use conflict acknowledgment state from composable - call only once
const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } =
useConflictAcknowledgment()
// Use either release red dot or conflict red dot
const shouldShowRedDot = computed((): boolean => {
const releaseRedDot = showReleaseRedDot.value
return releaseRedDot || shouldShowConflictRedDot.value
})
const sidebarLocation = computed(() =>
settingStore.get('Comfy.Sidebar.Location')
)
/**
* Toggle Help Center and track UI button click.
*/
const toggleHelpCenter = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: `${triggerFrom}_help_center_toggled`
})
helpCenterStore.toggle(triggerFrom)
}
const closeHelpCenter = () => {
helpCenterStore.hide()
}
/**
* Handle What's New popup dismissal
* Check if conflict modal should be shown after ComfyUI update
*/
const handleWhatsNewDismissed = async () => {
try {
// Check if conflict modal should be shown after update
const shouldShow =
await conflictDetection.shouldShowConflictModalAfterUpdate()
if (shouldShow) {
showConflictModal()
}
} catch (error) {
console.error('[HelpCenter] Error checking conflict modal:', error)
}
}
/**
* Show the node conflict dialog with current conflict data
*/
const showConflictModal = () => {
showNodeConflictDialog({
showAfterWhatsNew: true,
dialogComponentProps: {
onClose: () => {
markConflictsAsSeen()
}
}
})
}
// Initialize release store on mount
onMounted(async () => {
// Initialize release store to fetch releases for toast and popup
await releaseStore.initialize()
})
return {
isHelpCenterVisible,
triggerLocation,
shouldShowRedDot,
sidebarLocation,
toggleHelpCenter,
closeHelpCenter,
handleWhatsNewDismissed
}
}

View File

@@ -838,6 +838,7 @@
"customNodesManager": "Custom Nodes Manager", "customNodesManager": "Custom Nodes Manager",
"settings": "Settings", "settings": "Settings",
"help": "Help", "help": "Help",
"helpAndFeedback": "Help & Feedback",
"queue": "Queue Panel" "queue": "Queue Panel"
}, },
"tabMenu": { "tabMenu": {

View File

@@ -369,6 +369,14 @@
"Comfy_TreeExplorer_ItemPadding": { "Comfy_TreeExplorer_ItemPadding": {
"name": "Tree explorer item padding" "name": "Tree explorer item padding"
}, },
"Comfy_UI_TabBarLayout": {
"name": "Tab Bar Layout",
"tooltip": "Controls the layout of the tab bar. \"Integrated\" moves Help and User controls into the tab bar area.",
"options": {
"Default": "Default",
"Integrated": "Integrated"
}
},
"Comfy_UseNewMenu": { "Comfy_UseNewMenu": {
"name": "Use new menu", "name": "Use new menu",
"tooltip": "Enable the redesigned top menu bar.", "tooltip": "Enable the redesigned top menu bar.",

View File

@@ -549,6 +549,16 @@ export const CORE_SETTINGS: SettingParams[] = [
} }
} }
}, },
{
id: 'Comfy.UI.TabBarLayout',
category: ['Appearance', 'General'],
name: 'Tab Bar Layout',
type: 'combo',
options: ['Default', 'Integrated'],
tooltip:
'Controls the layout of the tab bar. "Integrated" moves Help and User controls into the tab bar area.',
defaultValue: 'Default'
},
{ {
id: 'Comfy.UseNewMenu', id: 'Comfy.UseNewMenu',
category: ['Comfy', 'Menu', 'UseNewMenu'], category: ['Comfy', 'Menu', 'UseNewMenu'],

View File

@@ -399,6 +399,7 @@ const zSettings = z.object({
'Comfy.Canvas.BackgroundImage': z.string().optional(), 'Comfy.Canvas.BackgroundImage': z.string().optional(),
'Comfy.ConfirmClear': z.boolean(), 'Comfy.ConfirmClear': z.boolean(),
'Comfy.DevMode': z.boolean(), 'Comfy.DevMode': z.boolean(),
'Comfy.UI.TabBarLayout': z.enum(['Default', 'Integrated']),
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(), 'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(), 'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
'Comfy.Workflow.WarnBlueprintOverwrite': z.boolean(), 'Comfy.Workflow.WarnBlueprintOverwrite': z.boolean(),

View File

@@ -1,14 +1,21 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
export type HelpCenterTriggerLocation = 'sidebar' | 'topbar'
export const useHelpCenterStore = defineStore('helpCenter', () => { export const useHelpCenterStore = defineStore('helpCenter', () => {
const isVisible = ref(false) const isVisible = ref(false)
const triggerLocation = ref<HelpCenterTriggerLocation>('sidebar')
const toggle = () => { const toggle = (location: HelpCenterTriggerLocation = 'sidebar') => {
if (!isVisible.value) {
triggerLocation.value = location
}
isVisible.value = !isVisible.value isVisible.value = !isVisible.value
} }
const show = () => { const show = (location: HelpCenterTriggerLocation = 'sidebar') => {
triggerLocation.value = location
isVisible.value = true isVisible.value = true
} }
@@ -18,6 +25,7 @@ export const useHelpCenterStore = defineStore('helpCenter', () => {
return { return {
isVisible, isVisible,
triggerLocation,
toggle, toggle,
show, show,
hide hide