mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Fix typecheck failures caused by backport conflict resolutions that brought in code depending on features not present on 1.41 branch.
This commit is contained in:
@@ -117,7 +117,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage, useMutationObserver } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
|
||||
@@ -291,13 +291,6 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (legacyContentCheckRafId === null) return
|
||||
|
||||
cancelAnimationFrame(legacyContentCheckRafId)
|
||||
legacyContentCheckRafId = null
|
||||
})
|
||||
|
||||
const openCustomNodeManager = async () => {
|
||||
try {
|
||||
await managerState.openManager({
|
||||
|
||||
@@ -13,17 +13,4 @@ const modelValue = defineModel<CurvePoint[]>({
|
||||
[1, 1]
|
||||
]
|
||||
})
|
||||
|
||||
const isDisabled = computed(() => !!widget.options?.disabled)
|
||||
|
||||
const upstreamValue = useUpstreamValue(
|
||||
() => widget.linkedUpstream,
|
||||
singleValueExtractor(isCurvePointArray)
|
||||
)
|
||||
|
||||
const effectivePoints = computed(() =>
|
||||
isDisabled.value && upstreamValue.value
|
||||
? upstreamValue.value
|
||||
: modelValue.value
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
import type { CurvePoint } from './types'
|
||||
|
||||
export function isCurvePointArray(value: unknown): value is CurvePoint[] {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length >= 2 &&
|
||||
value.every(
|
||||
(p) =>
|
||||
Array.isArray(p) &&
|
||||
p.length === 2 &&
|
||||
typeof p[0] === 'number' &&
|
||||
typeof p[1] === 'number'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Monotone cubic Hermite interpolation.
|
||||
* Produces a smooth curve that passes through all control points
|
||||
|
||||
@@ -116,23 +116,6 @@ const modelValue = defineModel<Bounds>({
|
||||
default: () => ({ x: 0, y: 0, width: 512, height: 512 })
|
||||
})
|
||||
|
||||
const isDisabled = computed(() => !!widget.options?.disabled)
|
||||
|
||||
const upstreamValue = useUpstreamValue(
|
||||
() => widget.linkedUpstream,
|
||||
boundsExtractor()
|
||||
)
|
||||
|
||||
const effectiveBounds = computed({
|
||||
get: () =>
|
||||
isDisabled.value && upstreamValue.value
|
||||
? upstreamValue.value
|
||||
: modelValue.value,
|
||||
set: (v) => {
|
||||
if (!isDisabled.value) modelValue.value = v
|
||||
}
|
||||
})
|
||||
|
||||
const imageEl = useTemplateRef<HTMLImageElement>('imageEl')
|
||||
const containerEl = useTemplateRef<HTMLDivElement>('containerEl')
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { WidgetState } from '@/stores/widgetValueStore'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
|
||||
import { boundsExtractor, singleValueExtractor } from './useUpstreamValue'
|
||||
|
||||
function widget(name: string, value: unknown): WidgetState {
|
||||
return { name, type: 'INPUT', value, nodeId: '1' as NodeId, options: {} }
|
||||
}
|
||||
|
||||
const isNumber = (v: unknown): v is number => typeof v === 'number'
|
||||
|
||||
describe('singleValueExtractor', () => {
|
||||
const extract = singleValueExtractor(isNumber)
|
||||
|
||||
it('matches widget by outputName', () => {
|
||||
const widgets = [widget('a', 'text'), widget('b', 42)]
|
||||
expect(extract(widgets, 'b')).toBe(42)
|
||||
})
|
||||
|
||||
it('returns undefined when outputName widget has invalid value', () => {
|
||||
const widgets = [widget('a', 'text'), widget('b', 'not a number')]
|
||||
expect(extract(widgets, 'b')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('falls back to unique valid widget when outputName has no match', () => {
|
||||
const widgets = [widget('a', 'text'), widget('b', 42)]
|
||||
expect(extract(widgets, 'missing')).toBe(42)
|
||||
})
|
||||
|
||||
it('falls back to unique valid widget when no outputName provided', () => {
|
||||
const widgets = [widget('a', 'text'), widget('b', 42)]
|
||||
expect(extract(widgets, undefined)).toBe(42)
|
||||
})
|
||||
|
||||
it('returns undefined when multiple widgets have valid values', () => {
|
||||
const widgets = [widget('a', 1), widget('b', 2)]
|
||||
expect(extract(widgets, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns undefined when no widgets have valid values', () => {
|
||||
const widgets = [widget('a', 'text')]
|
||||
expect(extract(widgets, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns undefined for empty widgets', () => {
|
||||
expect(extract([], undefined)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('boundsExtractor', () => {
|
||||
const extract = boundsExtractor()
|
||||
|
||||
it('extracts a single bounds object widget', () => {
|
||||
const bounds = { x: 10, y: 20, width: 100, height: 200 }
|
||||
const widgets = [widget('crop', bounds)]
|
||||
expect(extract(widgets, undefined)).toEqual(bounds)
|
||||
})
|
||||
|
||||
it('matches bounds widget by outputName', () => {
|
||||
const bounds = { x: 1, y: 2, width: 3, height: 4 }
|
||||
const widgets = [widget('other', 'text'), widget('crop', bounds)]
|
||||
expect(extract(widgets, 'crop')).toEqual(bounds)
|
||||
})
|
||||
|
||||
it('assembles bounds from individual x/y/width/height widgets', () => {
|
||||
const widgets = [
|
||||
widget('x', 10),
|
||||
widget('y', 20),
|
||||
widget('width', 100),
|
||||
widget('height', 200)
|
||||
]
|
||||
expect(extract(widgets, undefined)).toEqual({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 200
|
||||
})
|
||||
})
|
||||
|
||||
it('returns undefined when some bound components are missing', () => {
|
||||
const widgets = [widget('x', 10), widget('y', 20), widget('width', 100)]
|
||||
expect(extract(widgets, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns undefined when bound components have wrong types', () => {
|
||||
const widgets = [
|
||||
widget('x', '10'),
|
||||
widget('y', 20),
|
||||
widget('width', 100),
|
||||
widget('height', 200)
|
||||
]
|
||||
expect(extract(widgets, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns undefined for empty widgets', () => {
|
||||
expect(extract([], undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects partial bounds objects', () => {
|
||||
const partial = { x: 10, y: 20 }
|
||||
const widgets = [widget('crop', partial)]
|
||||
expect(extract(widgets, undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('prefers single bounds object over individual widgets', () => {
|
||||
const bounds = { x: 1, y: 2, width: 3, height: 4 }
|
||||
const widgets = [
|
||||
widget('crop', bounds),
|
||||
widget('x', 99),
|
||||
widget('y', 99),
|
||||
widget('width', 99),
|
||||
widget('height', 99)
|
||||
]
|
||||
expect(extract(widgets, undefined)).toEqual(bounds)
|
||||
})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
import type { WidgetState } from '@/stores/widgetValueStore'
|
||||
import type { Bounds } from '@/renderer/core/layout/types'
|
||||
import type { LinkedUpstreamInfo } from '@/types/simplifiedWidget'
|
||||
|
||||
type ValueExtractor<T = unknown> = (
|
||||
widgets: WidgetState[],
|
||||
outputName: string | undefined
|
||||
) => T | undefined
|
||||
|
||||
export function useUpstreamValue<T>(
|
||||
getLinkedUpstream: () => LinkedUpstreamInfo | undefined,
|
||||
extractValue: ValueExtractor<T>
|
||||
) {
|
||||
const canvasStore = useCanvasStore()
|
||||
const widgetValueStore = useWidgetValueStore()
|
||||
|
||||
return computed(() => {
|
||||
const upstream = getLinkedUpstream()
|
||||
if (!upstream) return undefined
|
||||
const graphId = canvasStore.canvas?.graph?.rootGraph.id
|
||||
if (!graphId) return undefined
|
||||
const widgets = widgetValueStore.getNodeWidgets(graphId, upstream.nodeId)
|
||||
return extractValue(widgets, upstream.outputName)
|
||||
})
|
||||
}
|
||||
|
||||
export function singleValueExtractor<T>(
|
||||
isValid: (value: unknown) => value is T
|
||||
): ValueExtractor<T> {
|
||||
return (widgets, outputName) => {
|
||||
if (outputName) {
|
||||
const matched = widgets.find((w) => w.name === outputName)
|
||||
if (matched && isValid(matched.value)) return matched.value
|
||||
}
|
||||
const validValues = widgets.map((w) => w.value).filter(isValid)
|
||||
return validValues.length === 1 ? validValues[0] : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function isBoundsObject(value: unknown): value is Bounds {
|
||||
if (typeof value !== 'object' || value === null) return false
|
||||
const v = value as Record<string, unknown>
|
||||
return (
|
||||
typeof v.x === 'number' &&
|
||||
typeof v.y === 'number' &&
|
||||
typeof v.width === 'number' &&
|
||||
typeof v.height === 'number'
|
||||
)
|
||||
}
|
||||
|
||||
export function boundsExtractor(): ValueExtractor<Bounds> {
|
||||
const single = singleValueExtractor(isBoundsObject)
|
||||
return (widgets, outputName) => {
|
||||
const singleResult = single(widgets, outputName)
|
||||
if (singleResult) return singleResult
|
||||
|
||||
// Fallback: assemble from individual widgets matching BoundingBoxInputSpec field names
|
||||
const getNum = (name: string): number | undefined => {
|
||||
const w = widgets.find((w) => w.name === name)
|
||||
return typeof w?.value === 'number' ? w.value : undefined
|
||||
}
|
||||
const x = getNum('x')
|
||||
const y = getNum('y')
|
||||
const width = getNum('width')
|
||||
const height = getNum('height')
|
||||
if (
|
||||
x !== undefined &&
|
||||
y !== undefined &&
|
||||
width !== undefined &&
|
||||
height !== undefined
|
||||
) {
|
||||
return { x, y, width, height }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
|
||||
|
||||
const mockPrivateModelsEnabled = vi.hoisted(() => ({ value: true }))
|
||||
const mockShowUploadDialog = vi.hoisted(() => vi.fn())
|
||||
const mockHandleUrlInput = vi.hoisted(() => vi.fn())
|
||||
const mockHandleImport = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
||||
useFeatureFlags: () => ({
|
||||
flags: {
|
||||
get privateModelsEnabled() {
|
||||
return mockPrivateModelsEnabled.value
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/assets/composables/useModelUpload', () => ({
|
||||
useModelUpload: () => ({
|
||||
isUploadButtonEnabled: { value: true },
|
||||
showUploadDialog: mockShowUploadDialog
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/platform/missingModel/composables/useMissingModelInteractions',
|
||||
() => ({
|
||||
useMissingModelInteractions: () => ({
|
||||
handleUrlInput: mockHandleUrlInput,
|
||||
handleImport: mockHandleImport
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/components/rightSidePanel/layout/TransitionCollapse.vue', () => ({
|
||||
default: {
|
||||
name: 'TransitionCollapse',
|
||||
template: '<div><slot /></div>'
|
||||
}
|
||||
}))
|
||||
|
||||
import MissingModelUrlInput from './MissingModelUrlInput.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: { loading: 'Loading' },
|
||||
rightSidePanel: {
|
||||
missingModels: {
|
||||
urlPlaceholder: 'Paste model URL...',
|
||||
clearUrl: 'Clear URL',
|
||||
import: 'Import',
|
||||
importAnyway: 'Import Anyway',
|
||||
typeMismatch: 'Type mismatch: {detectedType}',
|
||||
unsupportedUrl: 'Unsupported URL',
|
||||
metadataFetchFailed: 'Failed to fetch metadata',
|
||||
importFailed: 'Import failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
missingWarn: false,
|
||||
fallbackWarn: false
|
||||
})
|
||||
|
||||
const MODEL_KEY = 'supported::checkpoints::model.safetensors'
|
||||
|
||||
function mountComponent(
|
||||
props: Partial<{
|
||||
modelKey: string
|
||||
directory: string | null
|
||||
typeMismatch: string | null
|
||||
}> = {}
|
||||
) {
|
||||
return mount(MissingModelUrlInput, {
|
||||
props: {
|
||||
modelKey: MODEL_KEY,
|
||||
directory: 'checkpoints',
|
||||
typeMismatch: null,
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('MissingModelUrlInput', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
mockPrivateModelsEnabled.value = true
|
||||
mockShowUploadDialog.mockClear()
|
||||
mockHandleUrlInput.mockClear()
|
||||
mockHandleImport.mockClear()
|
||||
})
|
||||
|
||||
describe('URL input is always editable', () => {
|
||||
it('input is editable when privateModelsEnabled is true', () => {
|
||||
mockPrivateModelsEnabled.value = true
|
||||
const wrapper = mountComponent()
|
||||
const input = wrapper.find('input')
|
||||
expect(input.attributes('readonly')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('input is editable when privateModelsEnabled is false (free tier)', () => {
|
||||
mockPrivateModelsEnabled.value = false
|
||||
const wrapper = mountComponent()
|
||||
const input = wrapper.find('input')
|
||||
expect(input.attributes('readonly')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('input accepts user typing when privateModelsEnabled is false', async () => {
|
||||
mockPrivateModelsEnabled.value = false
|
||||
const wrapper = mountComponent()
|
||||
const input = wrapper.find('input')
|
||||
input.element.value = 'https://example.com/model.safetensors'
|
||||
await input.trigger('input')
|
||||
expect(mockHandleUrlInput).toHaveBeenCalledWith(
|
||||
MODEL_KEY,
|
||||
'https://example.com/model.safetensors'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Import button gates on subscription', () => {
|
||||
it('calls handleImport when privateModelsEnabled is true', async () => {
|
||||
mockPrivateModelsEnabled.value = true
|
||||
const store = useMissingModelStore()
|
||||
store.urlMetadata[MODEL_KEY] = {
|
||||
filename: 'model.safetensors',
|
||||
content_length: 1024,
|
||||
final_url: 'https://example.com/model.safetensors'
|
||||
}
|
||||
|
||||
const wrapper = mountComponent()
|
||||
const importBtn = wrapper
|
||||
.findAll('button')
|
||||
.find((b) => b.text().includes('Import'))
|
||||
expect(importBtn).toBeTruthy()
|
||||
await importBtn!.trigger('click')
|
||||
|
||||
expect(mockHandleImport).toHaveBeenCalledWith(MODEL_KEY, 'checkpoints')
|
||||
expect(mockShowUploadDialog).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('calls showUploadDialog when privateModelsEnabled is false (free tier)', async () => {
|
||||
mockPrivateModelsEnabled.value = false
|
||||
const store = useMissingModelStore()
|
||||
store.urlMetadata[MODEL_KEY] = {
|
||||
filename: 'model.safetensors',
|
||||
content_length: 1024,
|
||||
final_url: 'https://example.com/model.safetensors'
|
||||
}
|
||||
|
||||
const wrapper = mountComponent()
|
||||
const importBtn = wrapper
|
||||
.findAll('button')
|
||||
.find((b) => b.text().includes('Import'))
|
||||
expect(importBtn).toBeTruthy()
|
||||
await importBtn!.trigger('click')
|
||||
|
||||
expect(mockShowUploadDialog).toHaveBeenCalled()
|
||||
expect(mockHandleImport).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('clear button works for free-tier users', async () => {
|
||||
mockPrivateModelsEnabled.value = false
|
||||
const store = useMissingModelStore()
|
||||
store.urlInputs[MODEL_KEY] = 'https://example.com/model.safetensors'
|
||||
const wrapper = mountComponent()
|
||||
const clearBtn = wrapper.find('button[aria-label="Clear URL"]')
|
||||
await clearBtn.trigger('click')
|
||||
expect(mockHandleUrlInput).toHaveBeenCalledWith(MODEL_KEY, '')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex h-8 items-center rounded-lg border border-transparent bg-secondary-background px-3 transition-colors focus-within:border-interface-stroke"
|
||||
>
|
||||
<label :for="`url-input-${modelKey}`" class="sr-only">
|
||||
{{ t('rightSidePanel.missingModels.urlPlaceholder') }}
|
||||
</label>
|
||||
<input
|
||||
:id="`url-input-${modelKey}`"
|
||||
type="text"
|
||||
:value="urlInputs[modelKey] ?? ''"
|
||||
:placeholder="t('rightSidePanel.missingModels.urlPlaceholder')"
|
||||
class="text-foreground w-full border-none bg-transparent text-xs outline-none placeholder:text-muted-foreground"
|
||||
@input="
|
||||
handleUrlInput(modelKey, ($event.target as HTMLInputElement).value)
|
||||
"
|
||||
/>
|
||||
<Button
|
||||
v-if="urlInputs[modelKey]"
|
||||
variant="textonly"
|
||||
size="icon-sm"
|
||||
:aria-label="t('rightSidePanel.missingModels.clearUrl')"
|
||||
class="ml-1 shrink-0"
|
||||
@click.stop="handleUrlInput(modelKey, '')"
|
||||
>
|
||||
<i aria-hidden="true" class="icon-[lucide--x] size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<TransitionCollapse>
|
||||
<div v-if="urlMetadata[modelKey]" class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 px-0.5 pt-2.5">
|
||||
<span class="text-foreground min-w-0 truncate text-xs font-bold">
|
||||
{{ urlMetadata[modelKey]?.filename }}
|
||||
</span>
|
||||
<span
|
||||
v-if="(urlMetadata[modelKey]?.content_length ?? 0) > 0"
|
||||
class="shrink-0 rounded-sm bg-secondary-background-selected px-1.5 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
{{ formatSize(urlMetadata[modelKey]?.content_length ?? 0) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="typeMismatch" class="flex items-start gap-1.5 px-0.5">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mt-0.5 icon-[lucide--triangle-alert] size-3 shrink-0 text-warning-background"
|
||||
/>
|
||||
<span class="text-xs/tight text-warning-background">
|
||||
{{
|
||||
t('rightSidePanel.missingModels.typeMismatch', {
|
||||
detectedType: typeMismatch
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pt-0.5">
|
||||
<Button
|
||||
variant="primary"
|
||||
class="h-9 w-full justify-center gap-2 text-sm font-semibold"
|
||||
:loading="urlImporting[modelKey]"
|
||||
@click="handleImportClick"
|
||||
>
|
||||
<i aria-hidden="true" class="icon-[lucide--download] size-4" />
|
||||
{{
|
||||
typeMismatch
|
||||
? t('rightSidePanel.missingModels.importAnyway')
|
||||
: t('rightSidePanel.missingModels.import')
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionCollapse>
|
||||
|
||||
<TransitionCollapse>
|
||||
<div
|
||||
v-if="urlFetching[modelKey]"
|
||||
aria-live="polite"
|
||||
class="flex items-center justify-center py-2"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-[lucide--loader-circle] size-4 animate-spin text-muted-foreground"
|
||||
/>
|
||||
<span class="sr-only">{{ t('g.loading') }}</span>
|
||||
</div>
|
||||
</TransitionCollapse>
|
||||
|
||||
<TransitionCollapse>
|
||||
<div v-if="urlErrors[modelKey]" class="px-0.5" role="alert">
|
||||
<span class="text-xs text-destructive-background-hover">
|
||||
{{ urlErrors[modelKey] }}
|
||||
</span>
|
||||
</div>
|
||||
</TransitionCollapse>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import TransitionCollapse from '@/components/rightSidePanel/layout/TransitionCollapse.vue'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
|
||||
import { useMissingModelInteractions } from '@/platform/missingModel/composables/useMissingModelInteractions'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
|
||||
|
||||
const { modelKey, directory, typeMismatch } = defineProps<{
|
||||
modelKey: string
|
||||
directory: string | null
|
||||
typeMismatch: string | null
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { flags } = useFeatureFlags()
|
||||
const canImportModels = computed(() => flags.privateModelsEnabled)
|
||||
const { showUploadDialog } = useModelUpload()
|
||||
|
||||
const store = useMissingModelStore()
|
||||
const { urlInputs, urlMetadata, urlFetching, urlErrors, urlImporting } =
|
||||
storeToRefs(store)
|
||||
|
||||
const { handleUrlInput, handleImport } = useMissingModelInteractions()
|
||||
|
||||
function handleImportClick() {
|
||||
if (canImportModels.value) {
|
||||
handleImport(modelKey, directory)
|
||||
} else {
|
||||
showUploadDialog()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user