Files
ComfyUI_frontend/apps/desktop-ui/src/views/InstallView.vue
filtered 07a74e3cdc 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.
2025-10-05 05:04:27 +00:00

211 lines
6.1 KiB
Vue

<template>
<BaseViewTemplate dark>
<!-- Fixed height container with flexbox layout for proper content management -->
<div class="w-full h-full flex flex-col">
<Stepper
v-model:value="currentStep"
class="flex flex-col h-full"
@update:value="handleStepChange"
>
<!-- Main content area that grows to fill available space -->
<StepPanels
class="flex-1 overflow-auto"
:style="{ scrollbarGutter: 'stable' }"
>
<StepPanel value="1" class="flex">
<GpuPicker v-model:device="device" />
</StepPanel>
<StepPanel value="2">
<InstallLocationPicker
v-model:install-path="installPath"
v-model:path-error="pathError"
v-model:migration-source-path="migrationSourcePath"
v-model:migration-item-ids="migrationItemIds"
v-model:python-mirror="pythonMirror"
v-model:pypi-mirror="pypiMirror"
v-model:torch-mirror="torchMirror"
:device="device"
/>
</StepPanel>
<StepPanel value="3">
<DesktopSettingsConfiguration
v-model:auto-update="autoUpdate"
v-model:allow-metrics="allowMetrics"
/>
</StepPanel>
</StepPanels>
<!-- Install footer with navigation -->
<InstallFooter
class="w-full max-w-2xl my-6 mx-auto"
:current-step
:can-proceed
:disable-location-step="noGpu"
:disable-migration-step="noGpu || hasError || highestStep < 2"
:disable-settings-step="noGpu || hasError || highestStep < 3"
@previous="goToPreviousStep"
@next="goToNextStep"
@install="install"
/>
</Stepper>
</div>
</BaseViewTemplate>
</template>
<script setup lang="ts">
import type {
InstallOptions,
TorchDeviceType
} from '@comfyorg/comfyui-electron-types'
import StepPanel from 'primevue/steppanel'
import StepPanels from 'primevue/steppanels'
import Stepper from 'primevue/stepper'
import { computed, onMounted, ref, toRaw } from 'vue'
import { useRouter } from 'vue-router'
import DesktopSettingsConfiguration from '@/components/install/DesktopSettingsConfiguration.vue'
import GpuPicker from '@/components/install/GpuPicker.vue'
import InstallFooter from '@/components/install/InstallFooter.vue'
import InstallLocationPicker from '@/components/install/InstallLocationPicker.vue'
import { electronAPI } from '@/utils/envUtil'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const device = ref<TorchDeviceType | null>(null)
const installPath = ref('')
const pathError = ref('')
const migrationSourcePath = ref('')
const migrationItemIds = ref<string[]>([])
const autoUpdate = ref(true)
const allowMetrics = ref(true)
const pythonMirror = ref('')
const pypiMirror = ref('')
const torchMirror = ref('')
/** Current step in the stepper */
const currentStep = ref('1')
/** Forces each install step to be visited at least once. */
const highestStep = ref(0)
const handleStepChange = (value: string | number) => {
setHighestStep(value)
electronAPI().Events.trackEvent('install_stepper_change', {
step: value
})
}
const setHighestStep = (value: string | number) => {
const int = typeof value === 'number' ? value : parseInt(value, 10)
if (!isNaN(int) && int > highestStep.value) highestStep.value = int
}
const hasError = computed(() => pathError.value !== '')
const noGpu = computed(() => typeof device.value !== 'string')
// Computed property to determine if user can proceed to next step
const regex = /^Insufficient space - minimum free space: \d+ GB$/
const canProceed = computed(() => {
switch (currentStep.value) {
case '1':
return typeof device.value === 'string'
case '2':
return pathError.value === '' || regex.test(pathError.value)
case '3':
return !hasError.value
default:
return false
}
})
// Navigation methods
const goToNextStep = () => {
const nextStep = (parseInt(currentStep.value) + 1).toString()
currentStep.value = nextStep
setHighestStep(nextStep)
electronAPI().Events.trackEvent('install_stepper_change', {
step: nextStep
})
}
const goToPreviousStep = () => {
const prevStep = (parseInt(currentStep.value) - 1).toString()
currentStep.value = prevStep
electronAPI().Events.trackEvent('install_stepper_change', {
step: prevStep
})
}
const electron = electronAPI()
const router = useRouter()
const install = async () => {
const options: InstallOptions = {
installPath: installPath.value,
autoUpdate: autoUpdate.value,
allowMetrics: allowMetrics.value,
migrationSourcePath: migrationSourcePath.value,
migrationItemIds: toRaw(migrationItemIds.value),
pythonMirror: pythonMirror.value,
pypiMirror: pypiMirror.value,
torchMirror: torchMirror.value,
// @ts-expect-error fixme ts strict error
device: device.value
}
electron.installComfyUI(options)
const nextPage =
options.device === 'unsupported' ? '/manual-configuration' : '/server-start'
await router.push(nextPage)
}
onMounted(async () => {
if (!electron) return
const detectedGpu = await electron.Config.getDetectedGpu()
if (detectedGpu === 'mps' || detectedGpu === 'nvidia') {
device.value = detectedGpu
}
electronAPI().Events.trackEvent('install_stepper_change', {
step: currentStep.value,
gpu: detectedGpu
})
})
</script>
<style scoped>
@reference '../assets/css/style.css';
:deep(.p-steppanel) {
@apply mt-8 flex justify-center bg-transparent;
}
/* Remove default padding/margin from StepPanels to make scrollbar flush */
:deep(.p-steppanels) {
@apply p-0 m-0;
}
/* Ensure StepPanel content container has no top/bottom padding */
:deep(.p-steppanel-content) {
@apply p-0;
}
/* Custom overlay scrollbar for WebKit browsers (Electron, Chrome) */
:deep(.p-steppanels::-webkit-scrollbar) {
@apply w-4;
}
:deep(.p-steppanels::-webkit-scrollbar-track) {
@apply bg-transparent;
}
:deep(.p-steppanels::-webkit-scrollbar-thumb) {
@apply bg-white/20 rounded-lg border-[4px] border-transparent;
background-clip: content-box;
}
</style>