From 07a74e3cdcc1f2211c204b71dbabda4cf18183d1 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Sun, 5 Oct 2025 16:04:27 +1100 Subject: [PATCH] Decouple Desktop UI into monorepo app (#5912) ## Summary Extracts desktop UI into apps/desktop-ui package with minimal changes. ## Changes - **What**: - Separates desktop-specific code into standalone package with independent Vite config, router, and i18n - Drastically simplifies the main app router by removing all desktop routes - Adds a some code duplication, most due to the existing design - Some duplication can be refactored to be *simpler* on either side - no need to split things by `isElectron()` - Rudimentary storybook support has been added - **Breaking**: Stacked PR for publishing must be merged before this PR makes it to stable core (but publishing _could_ be done manually) - #5915 - **Dependencies**: Takes full advantage of pnpm catalog. No additional dependencies added. ## Review Focus - Should be no changes to normal frontend operation - Scripts added to root package.json are acceptable - The duplication in this PR is copied as is, wherever possible. Any corrections or fix-ups beyond the scope of simply migrating the functionality as-is, can be addressed in later PRs. That said, if any changes are made, it instantly becomes more difficult to separate the duplicated code out into a shared utility. - Tracking issue to address concerns: #5925 ### i18n Fixing i18n is out of scope for this PR. It is a larger task that we should consider carefully and implement properly. Attempting to isolate the desktop i18n and duplicate the _current_ localisation scripts would be wasted energy. --- .storybook/main.ts | 5 - CODEOWNERS | 7 +- apps/desktop-ui/.storybook/main.ts | 103 ++++++++++++++ apps/desktop-ui/.storybook/preview.ts | 88 ++++++++++++ apps/desktop-ui/index.html | 12 ++ apps/desktop-ui/package.json | 117 ++++++++++++++++ .../public}/assets/images/Git-Logo-White.svg | 0 .../public}/assets/images/apple-mps-logo.png | Bin .../assets/images/comfy-brand-mark.svg | 0 .../assets/images/nvidia-logo-square.jpg | Bin .../public}/assets/images/sad_girl.png | Bin apps/desktop-ui/src/App.vue | 7 + apps/desktop-ui/src/assets/css/style.css | 6 + .../tabs/terminal/BaseTerminal.vue | 113 +++++++++++++++ .../src}/components/common/RefreshButton.vue | 0 .../src}/components/common/StartupDisplay.vue | 0 .../src/components/common/UrlInput.vue | 129 ++++++++++++++++++ .../install/DesktopSettingsConfiguration.vue | 0 .../src}/components/install/GpuPicker.vue | 0 .../install/HardwareOption.stories.ts | 0 .../components/install/HardwareOption.vue | 0 .../src}/components/install/InstallFooter.vue | 0 .../install/InstallLocationPicker.stories.ts | 0 .../install/InstallLocationPicker.vue | 2 +- .../install/MigrationPicker.stories.ts | 0 .../components/install/MigrationPicker.vue | 0 .../components/install/mirror/MirrorItem.vue | 2 +- .../src}/components/maintenance/StatusTag.vue | 0 .../src}/components/maintenance/TaskCard.vue | 0 .../components/maintenance/TaskListItem.vue | 0 .../components/maintenance/TaskListPanel.vue | 0 .../maintenance/TaskListStatusIcon.vue | 0 .../maintenance/TerminalOutputDrawer.vue | 0 .../bottomPanelTabs/useTerminal.ts | 105 ++++++++++++++ .../bottomPanelTabs/useTerminalBuffer.ts | 0 .../src}/constants/desktopDialogs.ts | 0 .../src}/constants/desktopMaintenanceTasks.ts | 0 apps/desktop-ui/src/constants/uvMirrors.ts | 34 +++++ apps/desktop-ui/src/i18n.ts | 88 ++++++++++++ apps/desktop-ui/src/main.ts | 46 +++++++ apps/desktop-ui/src/router.ts | 92 +++++++++++++ .../src}/stores/maintenanceTaskStore.ts | 0 .../desktop-ui/src}/types/desktop/index.d.ts | 0 .../src}/types/desktop/maintenanceTypes.ts | 0 apps/desktop-ui/src/types/global.d.ts | 12 ++ .../desktop-ui/src}/types/primeVueTypes.ts | 0 .../src/utils/electronMirrorCheck.ts | 14 ++ apps/desktop-ui/src/utils/envUtil.ts | 13 ++ {src => apps/desktop-ui/src}/utils/refUtil.ts | 0 apps/desktop-ui/src/utils/tailwindUtil.ts | 1 + apps/desktop-ui/src/utils/validationUtil.ts | 6 + .../src}/views/DesktopDialogView.vue | 2 +- .../src}/views/DesktopStartView.vue | 0 .../src}/views/DesktopUpdateView.vue | 0 .../desktop-ui/src}/views/DownloadGitView.vue | 0 .../src}/views/InstallView.stories.ts | 0 .../desktop-ui/src}/views/InstallView.vue | 0 .../desktop-ui/src}/views/MaintenanceView.vue | 0 .../src}/views/ManualConfigurationView.vue | 0 .../src}/views/MetricsConsentView.vue | 0 .../src}/views/NotSupportedView.vue | 0 .../desktop-ui/src}/views/ServerStartView.vue | 0 .../desktop-ui/src}/views/WelcomeView.vue | 0 .../src/views/layouts/LayoutDefault.vue | 11 ++ .../src/views/templates/BaseViewTemplate.vue | 52 +++++++ apps/desktop-ui/tsconfig.json | 20 +++ apps/desktop-ui/vite.config.mts | 72 ++++++++++ package.json | 2 + pnpm-lock.yaml | 64 +++++++++ scripts/collect-i18n-general.ts | 2 +- src/constants/uvMirrors.ts | 8 +- src/router.ts | 79 ----------- 72 files changed, 1213 insertions(+), 101 deletions(-) create mode 100644 apps/desktop-ui/.storybook/main.ts create mode 100644 apps/desktop-ui/.storybook/preview.ts create mode 100644 apps/desktop-ui/index.html create mode 100644 apps/desktop-ui/package.json rename {public => apps/desktop-ui/public}/assets/images/Git-Logo-White.svg (100%) rename {public => apps/desktop-ui/public}/assets/images/apple-mps-logo.png (100%) rename {public => apps/desktop-ui/public}/assets/images/comfy-brand-mark.svg (100%) rename {public => apps/desktop-ui/public}/assets/images/nvidia-logo-square.jpg (100%) rename {public => apps/desktop-ui/public}/assets/images/sad_girl.png (100%) create mode 100644 apps/desktop-ui/src/App.vue create mode 100644 apps/desktop-ui/src/assets/css/style.css create mode 100644 apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue rename {src => apps/desktop-ui/src}/components/common/RefreshButton.vue (100%) rename {src => apps/desktop-ui/src}/components/common/StartupDisplay.vue (100%) create mode 100644 apps/desktop-ui/src/components/common/UrlInput.vue rename {src => apps/desktop-ui/src}/components/install/DesktopSettingsConfiguration.vue (100%) rename {src => apps/desktop-ui/src}/components/install/GpuPicker.vue (100%) rename {src => apps/desktop-ui/src}/components/install/HardwareOption.stories.ts (100%) rename {src => apps/desktop-ui/src}/components/install/HardwareOption.vue (100%) rename {src => apps/desktop-ui/src}/components/install/InstallFooter.vue (100%) rename {src => apps/desktop-ui/src}/components/install/InstallLocationPicker.stories.ts (100%) rename {src => apps/desktop-ui/src}/components/install/InstallLocationPicker.vue (99%) rename {src => apps/desktop-ui/src}/components/install/MigrationPicker.stories.ts (100%) rename {src => apps/desktop-ui/src}/components/install/MigrationPicker.vue (100%) rename {src => apps/desktop-ui/src}/components/install/mirror/MirrorItem.vue (97%) rename {src => apps/desktop-ui/src}/components/maintenance/StatusTag.vue (100%) rename {src => apps/desktop-ui/src}/components/maintenance/TaskCard.vue (100%) rename {src => apps/desktop-ui/src}/components/maintenance/TaskListItem.vue (100%) rename {src => apps/desktop-ui/src}/components/maintenance/TaskListPanel.vue (100%) rename {src => apps/desktop-ui/src}/components/maintenance/TaskListStatusIcon.vue (100%) rename {src => apps/desktop-ui/src}/components/maintenance/TerminalOutputDrawer.vue (100%) create mode 100644 apps/desktop-ui/src/composables/bottomPanelTabs/useTerminal.ts rename {src => apps/desktop-ui/src}/composables/bottomPanelTabs/useTerminalBuffer.ts (100%) rename {src => apps/desktop-ui/src}/constants/desktopDialogs.ts (100%) rename {src => apps/desktop-ui/src}/constants/desktopMaintenanceTasks.ts (100%) create mode 100644 apps/desktop-ui/src/constants/uvMirrors.ts create mode 100644 apps/desktop-ui/src/i18n.ts create mode 100644 apps/desktop-ui/src/main.ts create mode 100644 apps/desktop-ui/src/router.ts rename {src => apps/desktop-ui/src}/stores/maintenanceTaskStore.ts (100%) rename {src => apps/desktop-ui/src}/types/desktop/index.d.ts (100%) rename {src => apps/desktop-ui/src}/types/desktop/maintenanceTypes.ts (100%) create mode 100644 apps/desktop-ui/src/types/global.d.ts rename {src => apps/desktop-ui/src}/types/primeVueTypes.ts (100%) create mode 100644 apps/desktop-ui/src/utils/electronMirrorCheck.ts create mode 100644 apps/desktop-ui/src/utils/envUtil.ts rename {src => apps/desktop-ui/src}/utils/refUtil.ts (100%) create mode 100644 apps/desktop-ui/src/utils/tailwindUtil.ts create mode 100644 apps/desktop-ui/src/utils/validationUtil.ts rename {src => apps/desktop-ui/src}/views/DesktopDialogView.vue (95%) rename {src => apps/desktop-ui/src}/views/DesktopStartView.vue (100%) rename {src => apps/desktop-ui/src}/views/DesktopUpdateView.vue (100%) rename {src => apps/desktop-ui/src}/views/DownloadGitView.vue (100%) rename {src => apps/desktop-ui/src}/views/InstallView.stories.ts (100%) rename {src => apps/desktop-ui/src}/views/InstallView.vue (100%) rename {src => apps/desktop-ui/src}/views/MaintenanceView.vue (100%) rename {src => apps/desktop-ui/src}/views/ManualConfigurationView.vue (100%) rename {src => apps/desktop-ui/src}/views/MetricsConsentView.vue (100%) rename {src => apps/desktop-ui/src}/views/NotSupportedView.vue (100%) rename {src => apps/desktop-ui/src}/views/ServerStartView.vue (100%) rename {src => apps/desktop-ui/src}/views/WelcomeView.vue (100%) create mode 100644 apps/desktop-ui/src/views/layouts/LayoutDefault.vue create mode 100644 apps/desktop-ui/src/views/templates/BaseViewTemplate.vue create mode 100644 apps/desktop-ui/tsconfig.json create mode 100644 apps/desktop-ui/vite.config.mts diff --git a/.storybook/main.ts b/.storybook/main.ts index e8021974b..d61f72eae 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -76,11 +76,6 @@ const config: StorybookConfig = { }, build: { rollupOptions: { - external: () => { - // Don't externalize any modules in Storybook build - // This ensures PrimeVue and other dependencies are bundled - return false - }, onwarn: (warning, warn) => { // Suppress specific warnings if ( diff --git a/CODEOWNERS b/CODEOWNERS index cd1b4e508..d3517e2ab 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,12 +1,7 @@ # Desktop/Electron -/src/types/desktop/ @webfiltered -/src/constants/desktopDialogs.ts @webfiltered -/src/constants/desktopMaintenanceTasks.ts @webfiltered +/apps/desktop-ui/ @webfiltered /src/stores/electronDownloadStore.ts @webfiltered /src/extensions/core/electronAdapter.ts @webfiltered -/src/views/DesktopDialogView.vue @webfiltered -/src/components/install/ @webfiltered -/src/components/maintenance/ @webfiltered /vite.electron.config.mts @webfiltered # Common UI Components diff --git a/apps/desktop-ui/.storybook/main.ts b/apps/desktop-ui/.storybook/main.ts new file mode 100644 index 000000000..91c29eb7a --- /dev/null +++ b/apps/desktop-ui/.storybook/main.ts @@ -0,0 +1,103 @@ +import type { StorybookConfig } from '@storybook/vue3-vite' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import Components from 'unplugin-vue-components/vite' +import type { InlineConfig } from 'vite' + +const config: StorybookConfig = { + stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: ['@storybook/addon-docs'], + framework: { + name: '@storybook/vue3-vite', + options: {} + }, + staticDirs: [{ from: '../public', to: '/' }], + async viteFinal(config) { + // Use dynamic import to avoid CJS deprecation warning + const { mergeConfig } = await import('vite') + const { default: tailwindcss } = await import('@tailwindcss/vite') + + // Filter out any plugins that might generate import maps + if (config.plugins) { + config.plugins = config.plugins + // Type guard: ensure we have valid plugin objects with names + .filter( + (plugin): plugin is NonNullable & { name: string } => { + return ( + plugin !== null && + plugin !== undefined && + typeof plugin === 'object' && + 'name' in plugin && + typeof plugin.name === 'string' + ) + } + ) + // Business logic: filter out import-map plugins + .filter((plugin) => !plugin.name.includes('import-map')) + } + + return mergeConfig(config, { + // Replace plugins entirely to avoid inheritance issues + plugins: [ + // Only include plugins we explicitly need for Storybook + tailwindcss(), + Icons({ + compiler: 'vue3', + customCollections: { + comfy: FileSystemIconLoader( + process.cwd() + '/../../packages/design-system/src/icons' + ) + } + }), + Components({ + dts: false, // Disable dts generation in Storybook + resolvers: [ + IconsResolver({ + customCollections: ['comfy'] + }) + ], + dirs: [ + process.cwd() + '/src/components', + process.cwd() + '/src/views' + ], + deep: true, + extensions: ['vue'], + directoryAsNamespace: true + }) + ], + server: { + allowedHosts: true + }, + resolve: { + alias: { + '@': process.cwd() + '/src', + '@frontend-locales': process.cwd() + '/../../src/locales' + } + }, + build: { + rollupOptions: { + onwarn: (warning, warn) => { + // Suppress specific warnings + if ( + warning.code === 'UNUSED_EXTERNAL_IMPORT' && + warning.message?.includes('resolveComponent') + ) { + return + } + // Suppress Storybook font asset warnings + if ( + warning.code === 'UNRESOLVED_IMPORT' && + warning.message?.includes('nunito-sans') + ) { + return + } + warn(warning) + } + }, + chunkSizeWarningLimit: 1000 + } + } satisfies InlineConfig) + } +} +export default config diff --git a/apps/desktop-ui/.storybook/preview.ts b/apps/desktop-ui/.storybook/preview.ts new file mode 100644 index 000000000..a0ead30cc --- /dev/null +++ b/apps/desktop-ui/.storybook/preview.ts @@ -0,0 +1,88 @@ +import { definePreset } from '@primevue/themes' +import Aura from '@primevue/themes/aura' +import { setup } from '@storybook/vue3' +import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite' +import { createPinia } from 'pinia' +import 'primeicons/primeicons.css' +import PrimeVue from 'primevue/config' +import ConfirmationService from 'primevue/confirmationservice' +import ToastService from 'primevue/toastservice' +import Tooltip from 'primevue/tooltip' + +import '@/assets/css/style.css' +import { i18n } from '@/i18n' + +const ComfyUIPreset = definePreset(Aura, { + semantic: { + // @ts-expect-error prime type quirk + primary: Aura['primitive'].blue + } +}) + +setup((app) => { + app.directive('tooltip', Tooltip) + + const pinia = createPinia() + + app.use(pinia) + app.use(i18n) + app.use(PrimeVue, { + theme: { + preset: ComfyUIPreset, + options: { + prefix: 'p', + cssLayer: { name: 'primevue', order: 'primevue, tailwind-utilities' }, + darkModeSelector: '.dark-theme, :root:has(.dark-theme)' + } + } + }) + app.use(ConfirmationService) + app.use(ToastService) +}) + +export const withTheme = (Story: StoryFn, context: StoryContext) => { + const theme = context.globals.theme || 'light' + if (theme === 'dark') { + document.documentElement.classList.add('dark-theme') + document.body.classList.add('dark-theme') + } else { + document.documentElement.classList.remove('dark-theme') + document.body.classList.remove('dark-theme') + } + + return Story(context.args, context) +} + +const preview: Preview = { + parameters: { + controls: { + matchers: { color: /(background|color)$/i, date: /Date$/i } + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#0a0a0a' } + ] + } + }, + globalTypes: { + theme: { + name: 'Theme', + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + items: [ + { value: 'light', icon: 'sun', title: 'Light' }, + { value: 'dark', icon: 'moon', title: 'Dark' } + ], + showName: true, + dynamicTitle: true + } + } + }, + decorators: [withTheme] +} + +export default preview diff --git a/apps/desktop-ui/index.html b/apps/desktop-ui/index.html new file mode 100644 index 000000000..5cb34ffc2 --- /dev/null +++ b/apps/desktop-ui/index.html @@ -0,0 +1,12 @@ + + + + + ComfyUI Desktop + + + +
+ + + diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json new file mode 100644 index 000000000..686d35233 --- /dev/null +++ b/apps/desktop-ui/package.json @@ -0,0 +1,117 @@ +{ + "name": "@comfyorg/desktop-ui", + "version": "0.0.1", + "type": "module", + "nx": { + "tags": [ + "scope:desktop", + "type:app" + ], + "targets": { + "dev": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vite --config vite.config.mts" + } + }, + "serve": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vite --config vite.config.mts" + } + }, + "build": { + "executor": "nx:run-commands", + "cache": true, + "dependsOn": [ + "^build" + ], + "options": { + "cwd": "apps/desktop-ui", + "command": "vite build --config vite.config.mts" + }, + "outputs": [ + "{projectRoot}/dist" + ] + }, + "preview": { + "executor": "nx:run-commands", + "continuous": true, + "dependsOn": [ + "build" + ], + "options": { + "cwd": "apps/desktop-ui", + "command": "vite preview --config vite.config.mts" + } + }, + "storybook": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "storybook dev -p 6007" + } + }, + "build-storybook": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "storybook build -o dist/storybook" + }, + "outputs": [ + "{projectRoot}/dist/storybook" + ] + }, + "lint": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "eslint src --cache" + } + }, + "typecheck": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vue-tsc --noEmit -p tsconfig.json" + } + } + } + }, + "scripts": { + "storybook": "storybook dev -p 6007", + "build-storybook": "storybook build -o dist/storybook" + }, + "dependencies": { + "@comfyorg/comfyui-electron-types": "0.4.73-0", + "@comfyorg/shared-frontend-utils": "workspace:*", + "@primevue/core": "catalog:", + "@primevue/themes": "catalog:", + "@vueuse/core": "catalog:", + "pinia": "catalog:", + "primeicons": "catalog:", + "primevue": "catalog:", + "vue": "catalog:", + "vue-i18n": "catalog:", + "vue-router": "catalog:" + }, + "devDependencies": { + "@tailwindcss/vite": "catalog:", + "@vitejs/plugin-vue": "catalog:", + "dotenv": "catalog:", + "unplugin-icons": "catalog:", + "unplugin-vue-components": "catalog:", + "vite": "catalog:", + "vite-plugin-html": "catalog:", + "vite-plugin-vue-devtools": "catalog:", + "vue-tsc": "catalog:" + } +} diff --git a/public/assets/images/Git-Logo-White.svg b/apps/desktop-ui/public/assets/images/Git-Logo-White.svg similarity index 100% rename from public/assets/images/Git-Logo-White.svg rename to apps/desktop-ui/public/assets/images/Git-Logo-White.svg diff --git a/public/assets/images/apple-mps-logo.png b/apps/desktop-ui/public/assets/images/apple-mps-logo.png similarity index 100% rename from public/assets/images/apple-mps-logo.png rename to apps/desktop-ui/public/assets/images/apple-mps-logo.png diff --git a/public/assets/images/comfy-brand-mark.svg b/apps/desktop-ui/public/assets/images/comfy-brand-mark.svg similarity index 100% rename from public/assets/images/comfy-brand-mark.svg rename to apps/desktop-ui/public/assets/images/comfy-brand-mark.svg diff --git a/public/assets/images/nvidia-logo-square.jpg b/apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg similarity index 100% rename from public/assets/images/nvidia-logo-square.jpg rename to apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg diff --git a/public/assets/images/sad_girl.png b/apps/desktop-ui/public/assets/images/sad_girl.png similarity index 100% rename from public/assets/images/sad_girl.png rename to apps/desktop-ui/public/assets/images/sad_girl.png diff --git a/apps/desktop-ui/src/App.vue b/apps/desktop-ui/src/App.vue new file mode 100644 index 000000000..a43d49c99 --- /dev/null +++ b/apps/desktop-ui/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/desktop-ui/src/assets/css/style.css b/apps/desktop-ui/src/assets/css/style.css new file mode 100644 index 000000000..0eef377ae --- /dev/null +++ b/apps/desktop-ui/src/assets/css/style.css @@ -0,0 +1,6 @@ +@import '@comfyorg/design-system/css/style.css'; + +#desktop-app { + position: absolute; + inset: 0; +} diff --git a/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue new file mode 100644 index 000000000..ca0dcf34e --- /dev/null +++ b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/components/common/RefreshButton.vue b/apps/desktop-ui/src/components/common/RefreshButton.vue similarity index 100% rename from src/components/common/RefreshButton.vue rename to apps/desktop-ui/src/components/common/RefreshButton.vue diff --git a/src/components/common/StartupDisplay.vue b/apps/desktop-ui/src/components/common/StartupDisplay.vue similarity index 100% rename from src/components/common/StartupDisplay.vue rename to apps/desktop-ui/src/components/common/StartupDisplay.vue diff --git a/apps/desktop-ui/src/components/common/UrlInput.vue b/apps/desktop-ui/src/components/common/UrlInput.vue new file mode 100644 index 000000000..91f8cfe56 --- /dev/null +++ b/apps/desktop-ui/src/components/common/UrlInput.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/components/install/DesktopSettingsConfiguration.vue b/apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue similarity index 100% rename from src/components/install/DesktopSettingsConfiguration.vue rename to apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue diff --git a/src/components/install/GpuPicker.vue b/apps/desktop-ui/src/components/install/GpuPicker.vue similarity index 100% rename from src/components/install/GpuPicker.vue rename to apps/desktop-ui/src/components/install/GpuPicker.vue diff --git a/src/components/install/HardwareOption.stories.ts b/apps/desktop-ui/src/components/install/HardwareOption.stories.ts similarity index 100% rename from src/components/install/HardwareOption.stories.ts rename to apps/desktop-ui/src/components/install/HardwareOption.stories.ts diff --git a/src/components/install/HardwareOption.vue b/apps/desktop-ui/src/components/install/HardwareOption.vue similarity index 100% rename from src/components/install/HardwareOption.vue rename to apps/desktop-ui/src/components/install/HardwareOption.vue diff --git a/src/components/install/InstallFooter.vue b/apps/desktop-ui/src/components/install/InstallFooter.vue similarity index 100% rename from src/components/install/InstallFooter.vue rename to apps/desktop-ui/src/components/install/InstallFooter.vue diff --git a/src/components/install/InstallLocationPicker.stories.ts b/apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts similarity index 100% rename from src/components/install/InstallLocationPicker.stories.ts rename to apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts diff --git a/src/components/install/InstallLocationPicker.vue b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue similarity index 99% rename from src/components/install/InstallLocationPicker.vue rename to apps/desktop-ui/src/components/install/InstallLocationPicker.vue index 0e22f34a9..7d930d00e 100644 --- a/src/components/install/InstallLocationPicker.vue +++ b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue @@ -106,6 +106,7 @@ diff --git a/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue b/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue new file mode 100644 index 000000000..bbd6132ba --- /dev/null +++ b/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue @@ -0,0 +1,52 @@ + + + diff --git a/apps/desktop-ui/tsconfig.json b/apps/desktop-ui/tsconfig.json new file mode 100644 index 000000000..026fca248 --- /dev/null +++ b/apps/desktop-ui/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "allowImportingTsExtensions": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@frontend-locales/*": ["../../src/locales/*"] + } + }, + "include": [ + ".storybook/**/*", + "src/**/*.ts", + "src/**/*.vue", + "src/**/*.d.ts", + "vite.config.mts" + ], + "references": [] +} diff --git a/apps/desktop-ui/vite.config.mts b/apps/desktop-ui/vite.config.mts new file mode 100644 index 000000000..7cbc5307d --- /dev/null +++ b/apps/desktop-ui/vite.config.mts @@ -0,0 +1,72 @@ +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import dotenv from 'dotenv' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' +import { createHtmlPlugin } from 'vite-plugin-html' +import vueDevTools from 'vite-plugin-vue-devtools' + +dotenv.config() + +const projectRoot = fileURLToPath(new URL('.', import.meta.url)) + +const SHOULD_MINIFY = process.env.ENABLE_MINIFY === 'true' +const VITE_REMOTE_DEV = process.env.VITE_REMOTE_DEV === 'true' +const DISABLE_VUE_PLUGINS = process.env.DISABLE_VUE_PLUGINS === 'true' + +export default defineConfig(() => { + return { + root: projectRoot, + base: '', + publicDir: path.resolve(projectRoot, 'public'), + server: { + port: 5174, + host: VITE_REMOTE_DEV ? '0.0.0.0' : undefined + }, + resolve: { + alias: { + '@': path.resolve(projectRoot, 'src'), + '@frontend-locales': path.resolve(projectRoot, '../../src/locales') + } + }, + plugins: [ + ...(!DISABLE_VUE_PLUGINS + ? [vueDevTools(), vue(), createHtmlPlugin({})] + : [vue()]), + tailwindcss(), + Icons({ + compiler: 'vue3', + customCollections: { + comfy: FileSystemIconLoader( + path.resolve(projectRoot, '../../packages/design-system/src/icons') + ) + } + }), + Components({ + dts: path.resolve(projectRoot, 'components.d.ts'), + resolvers: [ + IconsResolver({ + customCollections: ['comfy'] + }) + ], + dirs: [ + path.resolve(projectRoot, 'src/components'), + path.resolve(projectRoot, 'src/views') + ], + deep: true, + extensions: ['vue'], + directoryAsNamespace: true + }) + ], + build: { + minify: SHOULD_MINIFY ? ('esbuild' as const) : false, + target: 'es2022', + sourcemap: true + } + } +}) diff --git a/package.json b/package.json index eb6afcb02..6192c38c9 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,12 @@ "description": "Official front-end implementation of ComfyUI", "license": "GPL-3.0-only", "scripts": { + "build:desktop": "nx build @comfyorg/desktop-ui", "build-storybook": "storybook build", "build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js", "build": "pnpm typecheck && nx build", "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", + "dev:desktop": "nx dev @comfyorg/desktop-ui", "dev:electron": "nx serve --config vite.electron.config.mts", "dev": "nx serve", "devtools:pycheck": "python3 -m compileall -q tools/devtools", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96d55214c..0b0b8a841 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -610,6 +610,70 @@ importers: specifier: 'catalog:' version: 3.24.1(zod@3.24.1) + apps/desktop-ui: + dependencies: + '@comfyorg/comfyui-electron-types': + specifier: 0.4.73-0 + version: 0.4.73-0 + '@comfyorg/shared-frontend-utils': + specifier: workspace:* + version: link:../../packages/shared-frontend-utils + '@primevue/core': + specifier: 'catalog:' + version: 4.2.5(vue@3.5.13(typescript@5.9.2)) + '@primevue/themes': + specifier: 'catalog:' + version: 4.2.5 + '@vueuse/core': + specifier: 'catalog:' + version: 11.0.0(vue@3.5.13(typescript@5.9.2)) + pinia: + specifier: 'catalog:' + version: 2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) + primeicons: + specifier: 'catalog:' + version: 7.0.0 + primevue: + specifier: 'catalog:' + version: 4.2.5(vue@3.5.13(typescript@5.9.2)) + vue: + specifier: 'catalog:' + version: 3.5.13(typescript@5.9.2) + vue-i18n: + specifier: 'catalog:' + version: 9.14.3(vue@3.5.13(typescript@5.9.2)) + vue-router: + specifier: 'catalog:' + version: 4.4.3(vue@3.5.13(typescript@5.9.2)) + devDependencies: + '@tailwindcss/vite': + specifier: 'catalog:' + version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + '@vitejs/plugin-vue': + specifier: 'catalog:' + version: 5.1.4(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) + dotenv: + specifier: 'catalog:' + version: 16.6.1 + unplugin-icons: + specifier: 'catalog:' + version: 0.22.0(@vue/compiler-sfc@3.5.13) + unplugin-vue-components: + specifier: 'catalog:' + version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)) + vite: + specifier: 'catalog:' + version: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) + vite-plugin-html: + specifier: 'catalog:' + version: 3.2.2(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + vite-plugin-vue-devtools: + specifier: 'catalog:' + version: 7.7.6(rollup@4.22.4)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) + vue-tsc: + specifier: 'catalog:' + version: 3.0.7(typescript@5.9.2) + packages/design-system: dependencies: '@iconify-json/lucide': diff --git a/scripts/collect-i18n-general.ts b/scripts/collect-i18n-general.ts index 8907133f1..1bbcb2060 100644 --- a/scripts/collect-i18n-general.ts +++ b/scripts/collect-i18n-general.ts @@ -1,12 +1,12 @@ import * as fs from 'fs' +import { DESKTOP_DIALOGS } from '../apps/desktop-ui/src/constants/desktopDialogs' import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage' import { formatCamelCase, normalizeI18nKey } from '../packages/shared-frontend-utils/src/formatUtil' import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands' -import { DESKTOP_DIALOGS } from '../src/constants/desktopDialogs' import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig' import type { FormItem, SettingParams } from '../src/platform/settings/types' import type { ComfyCommandImpl } from '../src/stores/commandStore' diff --git a/src/constants/uvMirrors.ts b/src/constants/uvMirrors.ts index 0a2c612ce..8e7e054c4 100644 --- a/src/constants/uvMirrors.ts +++ b/src/constants/uvMirrors.ts @@ -1,4 +1,4 @@ -export interface UVMirror { +interface UVMirror { /** * The setting id defined for the mirror. */ @@ -26,9 +26,3 @@ export const PYTHON_MIRROR: UVMirror = { validationPathSuffix: '/20250115/cpython-3.10.16+20250115-aarch64-apple-darwin-debug-full.tar.zst.sha256' } - -export const PYPI_MIRROR: UVMirror = { - settingId: 'Comfy-Desktop.UV.PypiInstallMirror', - mirror: 'https://pypi.org/simple/', - fallbackMirror: 'https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple' -} diff --git a/src/router.ts b/src/router.ts index f28b46030..cc51f7715 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,3 @@ -import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' import { createRouter, createWebHashHistory, @@ -13,18 +12,6 @@ import { isElectron } from './utils/envUtil' const isFileProtocol = window.location.protocol === 'file:' const basePath = isElectron() ? '/' : window.location.pathname -const guardElectronAccess = ( - _to: RouteLocationNormalized, - _from: RouteLocationNormalized, - next: NavigationGuardNext -) => { - if (isElectron()) { - next() - } else { - next('/') - } -} - const router = createRouter({ history: isFileProtocol ? createWebHashHistory() @@ -55,72 +42,6 @@ const router = createRouter({ path: 'user-select', name: 'UserSelectView', component: () => import('@/views/UserSelectView.vue') - }, - { - path: 'server-start', - name: 'ServerStartView', - component: () => import('@/views/ServerStartView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'install', - name: 'InstallView', - component: () => import('@/views/InstallView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'welcome', - name: 'WelcomeView', - component: () => import('@/views/WelcomeView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'not-supported', - name: 'NotSupportedView', - component: () => import('@/views/NotSupportedView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'download-git', - name: 'DownloadGitView', - component: () => import('@/views/DownloadGitView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'manual-configuration', - name: 'ManualConfigurationView', - component: () => import('@/views/ManualConfigurationView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: '/metrics-consent', - name: 'MetricsConsentView', - component: () => import('@/views/MetricsConsentView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'desktop-start', - name: 'DesktopStartView', - component: () => import('@/views/DesktopStartView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'maintenance', - name: 'MaintenanceView', - component: () => import('@/views/MaintenanceView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'desktop-update', - name: 'DesktopUpdateView', - component: () => import('@/views/DesktopUpdateView.vue'), - beforeEnter: guardElectronAccess - }, - { - path: 'desktop-dialog/:dialogId', - name: 'DesktopDialogView', - component: () => import('@/views/DesktopDialogView.vue'), - beforeEnter: guardElectronAccess } ] }