use update service

This commit is contained in:
bymyself
2025-11-26 15:32:21 -08:00
parent f5c26130f2
commit 93ec2c74ab
8 changed files with 142 additions and 29 deletions

View File

@@ -17,7 +17,6 @@ import { computed, onMounted } from 'vue'
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
import config from '@/config'
import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
@@ -48,7 +47,7 @@ const showContextMenu = (event: MouseEvent) => {
}
}
onMounted(async () => {
onMounted(() => {
window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version
if (isElectron()) {
@@ -78,25 +77,5 @@ onMounted(async () => {
// Initialize conflict detection in background
// This runs async and doesn't block UI setup
void conflictDetection.initializeConflictDetection()
// Show cloud notification for macOS desktop users (one-time)
// Delayed to ensure it appears after workflow loading (missing models dialog, etc.)
if (isElectron()) {
const isMacOS = navigator.platform.toLowerCase().includes('mac')
if (isMacOS) {
const settingStore = useSettingStore()
const hasShownNotification = settingStore.get(
'Comfy.Desktop.CloudNotificationShown'
)
if (!hasShownNotification) {
// Delay to show after initial workflow loading completes
setTimeout(async () => {
dialogService.showCloudNotification()
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
}, 2000)
}
}
}
})
</script>

View File

@@ -1,4 +1,4 @@
import tgpu from 'typegpu'
import typegpu from 'typegpu'
import * as d from 'typegpu/data'
import { BrushUniforms } from './gpuSchema'
@@ -38,7 +38,7 @@ fn vs(
}
`
export const brushVertex = tgpu.resolve({
export const brushVertex = typegpu.resolve({
template: brushVertexTemplate,
externals: {
BrushUniforms,
@@ -75,7 +75,7 @@ fn fs(v: VertexOutput) -> @location(0) vec4<f32> {
}
`
export const brushFragment = tgpu.resolve({
export const brushFragment = typegpu.resolve({
template: brushFragmentTemplate,
externals: {
VertexOutput,
@@ -100,7 +100,7 @@ const blitShaderTemplate = `
}
`
export const blitShader = tgpu.resolve({
export const blitShader = typegpu.resolve({
template: blitShaderTemplate,
externals: {}
})
@@ -123,7 +123,7 @@ const compositeShaderTemplate = `
}
`
export const compositeShader = tgpu.resolve({
export const compositeShader = typegpu.resolve({
template: compositeShaderTemplate,
externals: {
BrushUniforms
@@ -165,7 +165,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
}
`
export const readbackShader = tgpu.resolve({
export const readbackShader = typegpu.resolve({
template: readbackShaderTemplate,
externals: {}
})

View File

@@ -296,7 +296,7 @@ export const CORE_SETTINGS: SettingParams[] = [
{
id: 'Comfy.Desktop.CloudNotificationShown',
name: 'Cloud notification shown',
type: 'boolean',
type: 'hidden',
defaultValue: false
},
{

View File

@@ -45,6 +45,7 @@ function onChange(
export const useSettingStore = defineStore('setting', () => {
const settingValues = ref<Record<string, any>>({})
const settingsById = ref<Record<string, SettingParams>>({})
const isInitialized = ref(false)
/**
* Check if a setting's value exists, i.e. if the user has set it manually.
@@ -196,6 +197,7 @@ export const useSettingStore = defineStore('setting', () => {
// Migrate old zoom threshold setting to new font size setting
await migrateZoomThresholdToFontSize()
isInitialized.value = true
}
/**
@@ -240,6 +242,7 @@ export const useSettingStore = defineStore('setting', () => {
return {
settingValues,
settingsById,
isInitialized,
addSetting,
loadSettingValues,
set,

View File

@@ -0,0 +1,71 @@
import { until } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
export const useCloudNotificationStore = defineStore(
'cloudNotification',
() => {
const settingStore = useSettingStore()
const systemStatsStore = useSystemStatsStore()
const isReady = ref(false)
const hasShownThisSession = ref(false)
// Temporary override so developers can test the dialog in non-mac/non-desktop envs
const devOverrideEnabled = import.meta.env.DEV
async function initialize() {
if (isReady.value) return
await until(settingStore.isInitialized)
await until(systemStatsStore.isInitialized)
isReady.value = true
}
const isEligiblePlatform = computed(() => {
if (!isReady.value) return false
if (devOverrideEnabled) return true
if (!isElectron()) return false
const osString =
systemStatsStore.systemStats?.system?.os?.toLowerCase() ?? ''
const platformString =
typeof navigator === 'undefined' ? '' : navigator.platform.toLowerCase()
return (
osString.includes('darwin') ||
osString.includes('mac') ||
platformString.includes('mac')
)
})
const hasSeenNotification = computed(() => {
if (!isReady.value) return true
return !!settingStore.get('Comfy.Desktop.CloudNotificationShown')
})
const shouldShowNotification = computed(() => {
if (!isReady.value) return false
if (!isEligiblePlatform.value) return false
return !hasSeenNotification.value && !hasShownThisSession.value
})
function markSessionShown() {
hasShownThisSession.value = true
}
async function persistNotificationShown() {
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
}
return {
initialize,
shouldShowNotification,
markSessionShown,
persistNotificationShown
}
}
)

View File

@@ -0,0 +1,42 @@
<template>
<span class="sr-only" aria-hidden="true" />
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, watch } from 'vue'
import type { WatchStopHandle } from 'vue'
import { useCloudNotificationStore } from '@/platform/updates/common/cloudNotificationStore'
import { useDialogService } from '@/services/dialogService'
const dialogService = useDialogService()
const cloudNotificationStore = useCloudNotificationStore()
let stopWatcher: WatchStopHandle | null = null
onMounted(async () => {
await cloudNotificationStore.initialize()
stopWatcher = watch(
() => cloudNotificationStore.shouldShowNotification,
(shouldShow) => {
if (shouldShow) {
cloudNotificationStore.markSessionShown()
dialogService.showCloudNotification()
void cloudNotificationStore
.persistNotificationShown()
.catch((error) => {
console.error('[CloudNotification] Failed to persist flag', error)
})
}
},
{ immediate: true }
)
})
onBeforeUnmount(() => {
stopWatcher?.()
stopWatcher = null
})
</script>

View File

@@ -3,11 +3,17 @@ import type { useSettingStore } from '@/platform/settings/settingStore'
let pendingCallbacks: Array<() => Promise<void>> = []
let isNewUserDetermined = false
let isNewUserCached: boolean | null = null
// Temporary override to always treat dev builds as "new user" for local testing
const devForceNewUser = import.meta.env.DEV
export const newUserService = () => {
function checkIsNewUser(
settingStore: ReturnType<typeof useSettingStore>
): boolean {
if (devForceNewUser) {
return true
}
const isNewUserSettings =
Object.keys(settingStore.settingValues).length === 0 ||
!settingStore.get('Comfy.TutorialCompleted')
@@ -41,6 +47,16 @@ export const newUserService = () => {
isNewUserCached = checkIsNewUser(settingStore)
isNewUserDetermined = true
if (devForceNewUser) {
localStorage.removeItem('workflow')
localStorage.removeItem('Comfy.PreviousWorkflow')
try {
await settingStore.set('Comfy.TutorialCompleted', false)
} catch (error) {
console.error('[newUserService] Failed to reset tutorial flag', error)
}
}
if (!isNewUserCached) {
pendingCallbacks = []
return

View File

@@ -18,6 +18,7 @@
<GlobalToast />
<RerouteMigrationToast />
<VueNodesMigrationToast />
<CloudNotificationOrchestrator />
<UnloadWindowConfirmDialog v-if="!isElectron()" />
<MenuHamburger />
</template>
@@ -56,6 +57,7 @@ import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning'
import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'
import CloudNotificationOrchestrator from '@/platform/updates/components/CloudNotificationOrchestrator.vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import type { StatusWsMessageStatus } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'