mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 17:30:07 +00:00
Add UV mirrors settings (#2333)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -4,18 +4,18 @@
|
||||
v-bind="$attrs"
|
||||
:model-value="internalValue"
|
||||
class="w-full"
|
||||
:invalid="validationState === UrlValidationState.INVALID"
|
||||
:invalid="validationState === ValidationState.INVALID"
|
||||
@update:model-value="handleInput"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
<InputIcon
|
||||
:class="{
|
||||
'pi pi-spin pi-spinner text-neutral-400':
|
||||
validationState === UrlValidationState.LOADING,
|
||||
validationState === ValidationState.LOADING,
|
||||
'pi pi-check text-green-500 cursor-pointer':
|
||||
validationState === UrlValidationState.VALID,
|
||||
validationState === ValidationState.VALID,
|
||||
'pi pi-times text-red-500 cursor-pointer':
|
||||
validationState === UrlValidationState.INVALID
|
||||
validationState === ValidationState.INVALID
|
||||
}"
|
||||
@click="validateUrl(props.modelValue)"
|
||||
/>
|
||||
@@ -30,6 +30,7 @@ import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { isValidUrl } from '@/utils/formatUtil'
|
||||
import { checkUrlReachable } from '@/utils/networkUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
@@ -38,16 +39,10 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
'state-change': [state: ValidationState]
|
||||
}>()
|
||||
|
||||
enum UrlValidationState {
|
||||
IDLE = 'IDLE',
|
||||
LOADING = 'LOADING',
|
||||
VALID = 'VALID',
|
||||
INVALID = 'INVALID'
|
||||
}
|
||||
|
||||
const validationState = ref<UrlValidationState>(UrlValidationState.IDLE)
|
||||
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
||||
|
||||
// Add internal value state
|
||||
const internalValue = ref(props.modelValue)
|
||||
@@ -60,6 +55,11 @@ watch(
|
||||
await validateUrl(newValue)
|
||||
}
|
||||
)
|
||||
|
||||
watch(validationState, (newState) => {
|
||||
emit('state-change', newState)
|
||||
})
|
||||
|
||||
// Validate on mount
|
||||
onMounted(async () => {
|
||||
await validateUrl(props.modelValue)
|
||||
@@ -69,7 +69,7 @@ const handleInput = (value: string) => {
|
||||
// Update internal value without emitting
|
||||
internalValue.value = value
|
||||
// Reset validation state when user types
|
||||
validationState.value = UrlValidationState.IDLE
|
||||
validationState.value = ValidationState.IDLE
|
||||
}
|
||||
|
||||
const handleBlur = async () => {
|
||||
@@ -88,24 +88,24 @@ const defaultValidateUrl = async (url: string): Promise<boolean> => {
|
||||
}
|
||||
|
||||
const validateUrl = async (value: string) => {
|
||||
if (validationState.value === UrlValidationState.LOADING) return
|
||||
if (validationState.value === ValidationState.LOADING) return
|
||||
|
||||
const url = value.trim()
|
||||
|
||||
// Reset state
|
||||
validationState.value = UrlValidationState.IDLE
|
||||
validationState.value = ValidationState.IDLE
|
||||
|
||||
// Skip validation if empty
|
||||
if (!url) return
|
||||
|
||||
validationState.value = UrlValidationState.LOADING
|
||||
validationState.value = ValidationState.LOADING
|
||||
try {
|
||||
const isValid = await (props.validateUrlFn ?? defaultValidateUrl)(url)
|
||||
validationState.value = isValid
|
||||
? UrlValidationState.VALID
|
||||
: UrlValidationState.INVALID
|
||||
? ValidationState.VALID
|
||||
: ValidationState.INVALID
|
||||
} catch {
|
||||
validationState.value = UrlValidationState.INVALID
|
||||
validationState.value = ValidationState.INVALID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
70
src/components/install/MirrorsConfiguration.vue
Normal file
70
src/components/install/MirrorsConfiguration.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<Panel
|
||||
:header="$t('install.settings.mirrorSettings')"
|
||||
toggleable
|
||||
:collapsed="!showMirrorInputs"
|
||||
pt:root="bg-neutral-800 border-none w-[600px]"
|
||||
>
|
||||
<template
|
||||
v-for="([item, modelValue], index) in mirrors"
|
||||
:key="item.settingId"
|
||||
>
|
||||
<Divider v-if="index > 0" />
|
||||
|
||||
<MirrorItem
|
||||
:item="item"
|
||||
v-model="modelValue.value"
|
||||
@state-change="validationStates[index] = $event"
|
||||
/>
|
||||
</template>
|
||||
<template #icons>
|
||||
<i
|
||||
:class="{
|
||||
'pi pi-spin pi-spinner text-neutral-400':
|
||||
validationState === ValidationState.LOADING,
|
||||
'pi pi-check text-green-500':
|
||||
validationState === ValidationState.VALID,
|
||||
'pi pi-times text-red-500':
|
||||
validationState === ValidationState.INVALID
|
||||
}"
|
||||
v-tooltip="validationStateTooltip"
|
||||
/>
|
||||
</template>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import _ from 'lodash'
|
||||
import Divider from 'primevue/divider'
|
||||
import Panel from 'primevue/panel'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
|
||||
import { UV_MIRRORS } from '@/constants/uvMirrors'
|
||||
import { t } from '@/i18n'
|
||||
import { ValidationState, mergeValidationStates } from '@/utils/validationUtil'
|
||||
|
||||
const showMirrorInputs = ref(false)
|
||||
const pythonMirror = defineModel<string>('pythonMirror', { required: true })
|
||||
const pypiMirror = defineModel<string>('pypiMirror', { required: true })
|
||||
const torchMirror = defineModel<string>('torchMirror', { required: true })
|
||||
|
||||
const mirrors = _.zip(UV_MIRRORS, [pythonMirror, pypiMirror, torchMirror])
|
||||
|
||||
const validationStates = ref<ValidationState[]>(
|
||||
mirrors.map(() => ValidationState.IDLE)
|
||||
)
|
||||
const validationState = computed(() => {
|
||||
return mergeValidationStates(validationStates.value)
|
||||
})
|
||||
const validationStateTooltip = computed(() => {
|
||||
switch (validationState.value) {
|
||||
case ValidationState.INVALID:
|
||||
return t('install.settings.mirrorsUnreachable')
|
||||
case ValidationState.VALID:
|
||||
return t('install.settings.mirrorsReachable')
|
||||
default:
|
||||
return t('install.settings.checkingMirrors')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
66
src/components/install/mirror/MirrorItem.vue
Normal file
66
src/components/install/mirror/MirrorItem.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="w-full">
|
||||
<h3 class="text-lg font-medium text-neutral-100">
|
||||
{{ $t(`settings.${normalizedSettingId}.name`) }}
|
||||
</h3>
|
||||
<p class="text-sm text-neutral-400 mt-1">
|
||||
{{ $t(`settings.${normalizedSettingId}.tooltip`) }}
|
||||
</p>
|
||||
</div>
|
||||
<UrlInput
|
||||
v-model="modelValue"
|
||||
:validate-url-fn="checkMirrorReachable"
|
||||
@state-change="validationState = $event"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { UVMirror } from '@/constants/uvMirrors'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { isValidUrl, normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
const { item } = defineProps<{
|
||||
item: UVMirror
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'state-change': [state: ValidationState]
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<string>('modelValue', { required: true })
|
||||
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
||||
|
||||
const normalizedSettingId = computed(() => {
|
||||
return normalizeI18nKey(item.settingId)
|
||||
})
|
||||
|
||||
const checkMirrorReachable = async (mirror: string) => {
|
||||
return (
|
||||
isValidUrl(mirror) &&
|
||||
(await electronAPI().NetWork.canAccessUrl(
|
||||
mirror + (item.validationPathSuffix ?? '')
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
modelValue.value = item.mirror
|
||||
})
|
||||
|
||||
watch(validationState, (newState) => {
|
||||
emit('state-change', newState)
|
||||
|
||||
// Set fallback mirror if default mirror is invalid
|
||||
if (
|
||||
newState === ValidationState.INVALID &&
|
||||
modelValue.value === item.mirror
|
||||
) {
|
||||
modelValue.value = item.fallbackMirror
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user