mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[Electron] ComfyUI Desktop install wizard (#1503)
* Basic prototype * Welcome screen animation * nit * Refactor structure * Fix mocking * Add tooltips * i18n * Add next button * nit * Stepper navigate * Extract * More i18n * More i18n * Polish MigrationPicker * Polish settings step * Add more i18n * nit * nit
This commit is contained in:
112
src/components/install/DesktopSettingsConfiguration.vue
Normal file
112
src/components/install/DesktopSettingsConfiguration.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 w-[600px]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<h2 class="text-2xl font-semibold text-neutral-100">
|
||||
{{ $t('install.desktopAppSettings') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-neutral-400 my-0">
|
||||
{{ $t('install.desktopAppSettingsDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col bg-neutral-800 p-4 rounded-lg">
|
||||
<!-- Auto Update Setting -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-neutral-100">
|
||||
{{ $t('install.settings.autoUpdate') }}
|
||||
</h3>
|
||||
<p class="text-sm text-neutral-400 mt-1">
|
||||
{{ $t('install.settings.autoUpdateDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
<InputSwitch v-model="autoUpdate" />
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<!-- Metrics Collection Setting -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-neutral-100">
|
||||
{{ $t('install.settings.allowMetrics') }}
|
||||
</h3>
|
||||
<p class="text-sm text-neutral-400 mt-1">
|
||||
{{ $t('install.settings.allowMetricsDescription') }}
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm text-blue-400 hover:text-blue-300 mt-1 inline-block"
|
||||
@click.prevent="showMetricsInfo"
|
||||
>
|
||||
{{ $t('install.settings.learnMoreAboutData') }}
|
||||
</a>
|
||||
</div>
|
||||
<InputSwitch v-model="allowMetrics" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<Dialog
|
||||
v-model:visible="showDialog"
|
||||
modal
|
||||
:header="$t('install.settings.dataCollectionDialog.title')"
|
||||
>
|
||||
<div class="text-neutral-300">
|
||||
<h4 class="font-medium mb-2">
|
||||
{{ $t('install.settings.dataCollectionDialog.whatWeCollect') }}
|
||||
</h4>
|
||||
<ul class="list-disc pl-6 space-y-1">
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.errorReports') }}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.systemInfo') }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4 class="font-medium mt-4 mb-2">
|
||||
{{ $t('install.settings.dataCollectionDialog.whatWeDoNotCollect') }}
|
||||
</h4>
|
||||
<ul class="list-disc pl-6 space-y-1">
|
||||
<li>
|
||||
{{
|
||||
$t('install.settings.dataCollectionDialog.personalInformation')
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.workflowContents') }}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t('install.settings.dataCollectionDialog.fileSystemInformation')
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.customNodeConfigurations'
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import InputSwitch from 'primevue/inputswitch'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import Divider from 'primevue/divider'
|
||||
|
||||
const showDialog = ref(false)
|
||||
const autoUpdate = defineModel('autoUpdate', { required: true })
|
||||
const allowMetrics = defineModel('allowMetrics', { required: true })
|
||||
|
||||
const showMetricsInfo = () => {
|
||||
showDialog.value = true
|
||||
}
|
||||
</script>
|
||||
115
src/components/install/InstallLocationPicker.vue
Normal file
115
src/components/install/InstallLocationPicker.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 w-[600px]">
|
||||
<!-- Installation Path Section -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<h2 class="text-2xl font-semibold text-neutral-100">
|
||||
{{ $t('install.chooseInstallationLocation') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-neutral-400 my-0">
|
||||
{{ $t('install.installLocationDescription') }}
|
||||
</p>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<IconField class="flex-1">
|
||||
<InputText
|
||||
v-model="installPath"
|
||||
class="w-full"
|
||||
:class="{ 'p-invalid': pathError }"
|
||||
@change="validatePath"
|
||||
/>
|
||||
<InputIcon
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip="$t('install.installLocationTooltip')"
|
||||
/>
|
||||
</IconField>
|
||||
<Button icon="pi pi-folder" @click="browsePath" class="w-12" />
|
||||
</div>
|
||||
|
||||
<Message v-if="pathError" severity="error">
|
||||
{{ pathError }}
|
||||
</Message>
|
||||
</div>
|
||||
|
||||
<!-- System Paths Info -->
|
||||
<div class="bg-neutral-800 p-4 rounded-lg">
|
||||
<h3 class="text-lg font-medium mt-0 mb-3 text-neutral-100">
|
||||
{{ $t('install.systemLocations') }}
|
||||
</h3>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="pi pi-folder text-neutral-400" />
|
||||
<span class="text-neutral-400">App Data:</span>
|
||||
<span class="text-neutral-200">{{ appData }}</span>
|
||||
<span
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip="$t('install.appDataLocationTooltip')"
|
||||
></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="pi pi-desktop text-neutral-400" />
|
||||
<span class="text-neutral-400">App Path:</span>
|
||||
<span class="text-neutral-200">{{ appPath }}</span>
|
||||
<span
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip="$t('install.appPathLocationTooltip')"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Button from 'primevue/button'
|
||||
import IconField from 'primevue/iconfield'
|
||||
import InputIcon from 'primevue/inputicon'
|
||||
import Message from 'primevue/message'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const installPath = defineModel<string>('installPath', { required: true })
|
||||
const pathError = defineModel<string>('pathError', { required: true })
|
||||
const appData = ref('')
|
||||
const appPath = ref('')
|
||||
|
||||
// TODO: Implement the actual electron API.
|
||||
const electron = electronAPI() as any
|
||||
|
||||
// Get system paths on component mount
|
||||
onMounted(async () => {
|
||||
const paths = await electron.getSystemPaths()
|
||||
appData.value = paths.appData
|
||||
appPath.value = paths.appPath
|
||||
installPath.value = paths.defaultInstallPath
|
||||
})
|
||||
|
||||
const validatePath = async () => {
|
||||
try {
|
||||
pathError.value = ''
|
||||
const validation = await electron.validateInstallPath(installPath.value)
|
||||
|
||||
if (!validation.isValid) {
|
||||
pathError.value = validation.error
|
||||
}
|
||||
} catch (error) {
|
||||
pathError.value = t('install.pathValidationFailed')
|
||||
}
|
||||
}
|
||||
|
||||
const browsePath = async () => {
|
||||
try {
|
||||
const result = await electron.showDirectoryPicker()
|
||||
if (result) {
|
||||
installPath.value = result
|
||||
await validatePath()
|
||||
}
|
||||
} catch (error) {
|
||||
pathError.value = t('install.failedToSelectDirectory')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
136
src/components/install/MigrationPicker.vue
Normal file
136
src/components/install/MigrationPicker.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 w-[600px]">
|
||||
<!-- Source Location Section -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<h2 class="text-2xl font-semibold text-neutral-100">
|
||||
{{ $t('install.migrateFromExistingInstallation') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-neutral-400 my-0">
|
||||
{{ $t('install.migrationSourcePathDescription') }}
|
||||
</p>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<InputText
|
||||
v-model="sourcePath"
|
||||
placeholder="Select existing ComfyUI installation (optional)"
|
||||
class="flex-1"
|
||||
:class="{ 'p-invalid': pathError }"
|
||||
@change="validateSource"
|
||||
/>
|
||||
<Button icon="pi pi-folder" @click="browsePath" class="w-12" />
|
||||
</div>
|
||||
|
||||
<Message v-if="pathError" severity="error">
|
||||
{{ pathError }}
|
||||
</Message>
|
||||
</div>
|
||||
|
||||
<!-- Migration Options -->
|
||||
<div
|
||||
v-if="isValidSource"
|
||||
class="flex flex-col gap-4 bg-neutral-800 p-4 rounded-lg"
|
||||
>
|
||||
<h3 class="text-lg mt-0 font-medium text-neutral-100">
|
||||
{{ $t('install.selectItemsToMigrate') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="item in migrationItems"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-3 p-2 hover:bg-neutral-700 rounded"
|
||||
@click="item.selected = !item.selected"
|
||||
>
|
||||
<Checkbox
|
||||
v-model="item.selected"
|
||||
:inputId="item.id"
|
||||
:binary="true"
|
||||
@click.stop
|
||||
/>
|
||||
<div>
|
||||
<label :for="item.id" class="text-neutral-200 font-medium">
|
||||
{{ item.label }}
|
||||
</label>
|
||||
<p class="text-sm text-neutral-400 my-1">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skip Migration -->
|
||||
<div v-else class="text-neutral-400 italic">
|
||||
{{ $t('install.migrationOptional') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watchEffect } from 'vue'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Button from 'primevue/button'
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import Message from 'primevue/message'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const electron = electronAPI() as any
|
||||
|
||||
const sourcePath = defineModel<string>('sourcePath', { required: false })
|
||||
const migrationItemIds = defineModel<string[]>('migrationItemIds', {
|
||||
required: false
|
||||
})
|
||||
|
||||
const migrationItems = ref([])
|
||||
const pathError = ref('')
|
||||
const isValidSource = computed(
|
||||
() => sourcePath.value !== '' && pathError.value === ''
|
||||
)
|
||||
|
||||
const validateSource = async () => {
|
||||
if (!sourcePath.value) {
|
||||
pathError.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
pathError.value = ''
|
||||
const validation = await electron.validateComfyUISource(sourcePath.value)
|
||||
|
||||
if (!validation.isValid) pathError.value = validation.error
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
pathError.value = t('install.pathValidationFailed')
|
||||
}
|
||||
}
|
||||
|
||||
const browsePath = async () => {
|
||||
try {
|
||||
const result = await electron.showDirectoryPicker()
|
||||
if (result) {
|
||||
sourcePath.value = result
|
||||
await validateSource()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
pathError.value = t('install.failedToSelectDirectory')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
migrationItems.value = (await electron.migrationItems()).map((item) => ({
|
||||
...item,
|
||||
selected: true
|
||||
}))
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
migrationItemIds.value = migrationItems.value
|
||||
.filter((item) => item.selected)
|
||||
.map((item) => item.id)
|
||||
})
|
||||
</script>
|
||||
@@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center gap-8 p-8">
|
||||
<!-- Header -->
|
||||
<h1 class="text-4xl font-bold text-neutral-100">Welcome to ComfyUI</h1>
|
||||
|
||||
<!-- Get Started Button -->
|
||||
<Button
|
||||
label="Get Started"
|
||||
icon="pi pi-arrow-right"
|
||||
iconPos="right"
|
||||
size="large"
|
||||
@click="$emit('start')"
|
||||
class="p-4 text-lg"
|
||||
/>
|
||||
|
||||
<!-- Installation Steps -->
|
||||
<div class="w-[600px]">
|
||||
<Steps :model="installSteps" :readonly="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Steps from 'primevue/steps'
|
||||
import Button from 'primevue/button'
|
||||
|
||||
const installSteps = [
|
||||
{
|
||||
label: 'Install Location',
|
||||
icon: 'pi pi-folder'
|
||||
},
|
||||
{
|
||||
label: 'Migration',
|
||||
icon: 'pi pi-download'
|
||||
},
|
||||
{
|
||||
label: 'Desktop Settings',
|
||||
icon: 'pi pi-desktop'
|
||||
},
|
||||
{
|
||||
label: 'Review',
|
||||
icon: 'pi pi-check'
|
||||
}
|
||||
]
|
||||
|
||||
defineEmits(['start'])
|
||||
</script>
|
||||
45
src/i18n.ts
45
src/i18n.ts
@@ -2,6 +2,51 @@ import { createI18n } from 'vue-i18n'
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
install: {
|
||||
installLocation: 'Install Location',
|
||||
migration: 'Migration',
|
||||
desktopSettings: 'Desktop Settings',
|
||||
chooseInstallationLocation: 'Choose Installation Location',
|
||||
systemLocations: 'System Locations',
|
||||
failedToSelectDirectory: 'Failed to select directory',
|
||||
pathValidationFailed: 'Failed to validate path',
|
||||
installLocationDescription:
|
||||
"Select the directory for ComfyUI's user data. A python environment will be installed to the selected location. Please make sure the selected disk has enough space (~5GB) left.",
|
||||
installLocationTooltip:
|
||||
"ComfyUI's user data directory. Stores:\n- Python Environment\n- Models\n- Custom nodes\n",
|
||||
appDataLocationTooltip:
|
||||
"ComfyUI's app data directory. Stores:\n- Logs\n- Server configs",
|
||||
appPathLocationTooltip:
|
||||
"ComfyUI's app asset directory. Stores the ComfyUI code and assets",
|
||||
migrateFromExistingInstallation: 'Migrate from Existing Installation',
|
||||
migrationSourcePathDescription:
|
||||
'If you have an existing ComfyUI installation, we can copy/link your existing user files and models to the new installation.',
|
||||
selectItemsToMigrate: 'Select Items to Migrate',
|
||||
migrationOptional:
|
||||
"Migration is optional. If you don't have an existing installation, you can skip this step.",
|
||||
desktopAppSettings: 'Desktop App Settings',
|
||||
desktopAppSettingsDescription:
|
||||
'Configure how ComfyUI behaves on your desktop. You can change these settings later.',
|
||||
settings: {
|
||||
autoUpdate: 'Automatic Updates',
|
||||
allowMetrics: 'Usage Analytics',
|
||||
autoUpdateDescription:
|
||||
"Automatically download and install updates when they become available. You'll always be notified before updates are installed.",
|
||||
allowMetricsDescription:
|
||||
'Help improve ComfyUI by sending anonymous usage data. No personal information or workflow content will be collected.',
|
||||
learnMoreAboutData: 'Learn more about data collection',
|
||||
dataCollectionDialog: {
|
||||
title: 'About Data Collection',
|
||||
whatWeCollect: 'What we collect:',
|
||||
whatWeDoNotCollect: "What we don't collect:",
|
||||
errorReports: 'Error reports',
|
||||
systemInfo: 'Operating system and app version',
|
||||
personalInformation: 'Personal information',
|
||||
workflowContent: 'Workflow content',
|
||||
fileSystemInformation: 'File system information'
|
||||
}
|
||||
}
|
||||
},
|
||||
download: 'Download',
|
||||
loadAllFolders: 'Load All Folders',
|
||||
refresh: 'Refresh',
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
createWebHistory
|
||||
createWebHistory,
|
||||
NavigationGuardNext,
|
||||
RouteLocationNormalized
|
||||
} from 'vue-router'
|
||||
import LayoutDefault from '@/views/layouts/LayoutDefault.vue'
|
||||
import { isElectron } from './utils/envUtil'
|
||||
|
||||
const isFileProtocol = () => window.location.protocol === 'file:'
|
||||
const guardElectronAccess = (
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
next: NavigationGuardNext
|
||||
) => {
|
||||
if (isElectron()) {
|
||||
next()
|
||||
} else {
|
||||
next('/')
|
||||
}
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: isFileProtocol() ? createWebHashHistory() : createWebHistory(),
|
||||
@@ -24,27 +37,19 @@ const router = createRouter({
|
||||
path: 'server-start',
|
||||
name: 'ServerStartView',
|
||||
component: () => import('@/views/ServerStartView.vue'),
|
||||
beforeEnter: async (to, from, next) => {
|
||||
// Only allow access to this page in electron environment
|
||||
if (isElectron()) {
|
||||
next()
|
||||
} else {
|
||||
next('/')
|
||||
}
|
||||
}
|
||||
beforeEnter: guardElectronAccess
|
||||
},
|
||||
{
|
||||
path: 'install',
|
||||
name: 'InstallView',
|
||||
component: () => import('@/views/InstallView.vue'),
|
||||
beforeEnter: async (to, from, next) => {
|
||||
// Only allow access to this page in electron environment
|
||||
if (isElectron()) {
|
||||
next()
|
||||
} else {
|
||||
next('/')
|
||||
}
|
||||
}
|
||||
beforeEnter: guardElectronAccess
|
||||
},
|
||||
{
|
||||
path: 'welcome',
|
||||
name: 'WelcomeView',
|
||||
component: () => import('@/views/WelcomeView.vue'),
|
||||
beforeEnter: guardElectronAccess
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,118 @@
|
||||
<template>
|
||||
<div
|
||||
class="font-sans flex flex-col justify-center items-center h-screen m-0 text-neutral-300 bg-neutral-900 dark-theme pointer-events-auto"
|
||||
class="font-sans flex flex-col items-center h-screen m-0 text-neutral-300 bg-neutral-900 dark-theme pointer-events-auto"
|
||||
>
|
||||
<WelcomeScreen />
|
||||
<Stepper class="mt-[5vh] 2xl:mt-[20vh]" value="1">
|
||||
<StepList>
|
||||
<Step value="1" :disabled="hasError">
|
||||
{{ $t('install.installLocation') }}
|
||||
</Step>
|
||||
<Step value="2" :disabled="hasError">
|
||||
{{ $t('install.migration') }}
|
||||
</Step>
|
||||
<Step value="3" :disabled="hasError">
|
||||
{{ $t('install.desktopSettings') }}
|
||||
</Step>
|
||||
</StepList>
|
||||
<StepPanels>
|
||||
<StepPanel value="1" v-slot="{ activateCallback }">
|
||||
<InstallLocationPicker
|
||||
v-model:installPath="installPath"
|
||||
v-model:pathError="pathError"
|
||||
/>
|
||||
<div class="flex pt-6 justify-end">
|
||||
<Button
|
||||
label="Next"
|
||||
icon="pi pi-arrow-right"
|
||||
iconPos="right"
|
||||
@click="activateCallback('2')"
|
||||
:disabled="pathError !== ''"
|
||||
/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
<StepPanel value="2" v-slot="{ activateCallback }">
|
||||
<MigrationPicker
|
||||
v-model:sourcePath="migrationSourcePath"
|
||||
v-model:migrationItemIds="migrationItemIds"
|
||||
/>
|
||||
<div class="flex pt-6 justify-between">
|
||||
<Button
|
||||
label="Back"
|
||||
severity="secondary"
|
||||
icon="pi pi-arrow-left"
|
||||
@click="activateCallback('1')"
|
||||
/>
|
||||
<Button
|
||||
label="Next"
|
||||
icon="pi pi-arrow-right"
|
||||
iconPos="right"
|
||||
@click="activateCallback('3')"
|
||||
/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
<StepPanel value="3" v-slot="{ activateCallback }">
|
||||
<DesktopSettingsConfiguration
|
||||
v-model:autoUpdate="autoUpdate"
|
||||
v-model:allowMetrics="allowMetrics"
|
||||
/>
|
||||
<div class="flex pt-6 justify-between">
|
||||
<Button
|
||||
label="Back"
|
||||
severity="secondary"
|
||||
icon="pi pi-arrow-left"
|
||||
@click="activateCallback('2')"
|
||||
/>
|
||||
<Button
|
||||
label="Install"
|
||||
icon="pi pi-check"
|
||||
iconPos="right"
|
||||
@click="install()"
|
||||
/>
|
||||
</div>
|
||||
</StepPanel>
|
||||
</StepPanels>
|
||||
</Stepper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WelcomeScreen from '@/components/install/WelcomeScreen.vue'
|
||||
import Button from 'primevue/button'
|
||||
import Stepper from 'primevue/stepper'
|
||||
import StepList from 'primevue/steplist'
|
||||
import StepPanels from 'primevue/steppanels'
|
||||
import Step from 'primevue/step'
|
||||
import StepPanel from 'primevue/steppanel'
|
||||
|
||||
import InstallLocationPicker from '@/components/install/InstallLocationPicker.vue'
|
||||
import MigrationPicker from '@/components/install/MigrationPicker.vue'
|
||||
import DesktopSettingsConfiguration from '@/components/install/DesktopSettingsConfiguration.vue'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const installPath = ref('')
|
||||
const pathError = ref('')
|
||||
|
||||
const migrationSourcePath = ref('')
|
||||
const migrationItemIds = ref<string[]>([])
|
||||
|
||||
const autoUpdate = ref(true)
|
||||
const allowMetrics = ref(true)
|
||||
|
||||
const hasError = computed(() => pathError.value !== '')
|
||||
|
||||
const install = () => {
|
||||
;(electronAPI() as any).installComfyUI({
|
||||
installPath: installPath.value,
|
||||
autoUpdate: autoUpdate.value,
|
||||
allowMetrics: allowMetrics.value,
|
||||
migrationSourcePath: migrationSourcePath.value,
|
||||
migrationItemIds: migrationItemIds.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-steppanel) {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
69
src/views/WelcomeView.vue
Normal file
69
src/views/WelcomeView.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div
|
||||
class="font-sans flex flex-col justify-center items-center h-screen m-0 text-neutral-300 bg-neutral-900 dark-theme pointer-events-auto"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center gap-8 p-8">
|
||||
<!-- Header -->
|
||||
<h1 class="animated-gradient-text text-glow">Welcome to ComfyUI</h1>
|
||||
|
||||
<!-- Get Started Button -->
|
||||
<Button
|
||||
label="Get Started"
|
||||
icon="pi pi-arrow-right"
|
||||
iconPos="right"
|
||||
size="large"
|
||||
rounded
|
||||
@click="$router.push('/install')"
|
||||
class="p-4 text-lg fade-in-up"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.animated-gradient-text {
|
||||
@apply font-bold;
|
||||
font-size: clamp(2rem, 8vw, 4rem);
|
||||
background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59, #12c2e9);
|
||||
background-size: 300% auto;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: gradient 8s linear infinite;
|
||||
}
|
||||
|
||||
.text-glow {
|
||||
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.3));
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% center;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 300% center;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-up {
|
||||
animation: fadeInUp 1.5s ease-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,16 +2,6 @@ import { defineConfig, Plugin } from 'vite'
|
||||
import { mergeConfig } from 'vite'
|
||||
import type { UserConfig } from 'vitest/config'
|
||||
import baseConfig from './vite.config.mts'
|
||||
import type { ElectronAPI } from '@comfyorg/comfyui-electron-types'
|
||||
|
||||
const electronAPIMock: Partial<ElectronAPI> = {
|
||||
sendReady: () => {},
|
||||
onShowSelectDirectory: () => {},
|
||||
onFirstTimeSetupComplete: () => {},
|
||||
onProgressUpdate: () => {},
|
||||
onLogMessage: () => {},
|
||||
isFirstTimeSetup: () => Promise.resolve(true)
|
||||
}
|
||||
|
||||
const mockElectronAPI: Plugin = {
|
||||
name: 'mock-electron-api',
|
||||
@@ -19,7 +9,41 @@ const mockElectronAPI: Plugin = {
|
||||
return [
|
||||
{
|
||||
tag: 'script',
|
||||
children: `window.electronAPI = ${JSON.stringify(electronAPIMock)};`
|
||||
children: `window.electronAPI = {
|
||||
sendReady: () => {},
|
||||
onShowSelectDirectory: () => {},
|
||||
onFirstTimeSetupComplete: () => {},
|
||||
onProgressUpdate: () => {},
|
||||
onLogMessage: () => {},
|
||||
isFirstTimeSetup: () => Promise.resolve(true),
|
||||
getSystemPaths: () =>
|
||||
Promise.resolve({
|
||||
appData: 'C:/Users/username/AppData/Roaming',
|
||||
appPath: 'C:/Program Files/comfyui-electron/resources/app',
|
||||
defaultInstallPath: 'C:/Users/username/comfyui-electron'
|
||||
}),
|
||||
validateInstallPath: (path) => {
|
||||
if (path === 'bad') {
|
||||
return { isValid: false, error: 'Bad path!' }
|
||||
}
|
||||
return { isValid: true }
|
||||
},
|
||||
migrationItems: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 'user_files',
|
||||
label: 'User Files',
|
||||
description: 'Settings and user-created workflows'
|
||||
}
|
||||
]),
|
||||
validateComfyUISource: (path) => {
|
||||
if (path === 'bad') {
|
||||
return { isValid: false, error: 'Bad path!' }
|
||||
}
|
||||
return { isValid: true }
|
||||
},
|
||||
showDirectoryPicker: () => Promise.resolve('C:/Users/username/comfyui-electron/1')
|
||||
};`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user