Skip to content

Commit 3f34c9e

Browse files
code review comments
1 parent f5564fd commit 3f34c9e

File tree

7 files changed

+235
-197
lines changed

7 files changed

+235
-197
lines changed

src/locales/en/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,7 @@
20782078
"uploadModel": "Upload model",
20792079
"uploadModelFromCivitai": "Upload a model from Civitai",
20802080
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
2081+
"onlyCivitaiUrlsSupported": "Only Civitai URLs are supported",
20812082
"uploadModelDescription1": "Paste a Civitai model download link to add it to your library.",
20822083
"uploadModelDescription2": "Only links from https://civitai.com are supported at the moment",
20832084
"uploadModelDescription3": "Max file size: 1 GB",

src/platform/assets/components/UploadModelDialog.vue

Lines changed: 22 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,20 @@
3131
:can-fetch-metadata="canFetchMetadata"
3232
:can-upload-model="canUploadModel"
3333
:upload-status="uploadStatus"
34-
:on-back="goToPreviousStep"
35-
:on-fetch-metadata="handleFetchMetadata"
36-
:on-upload="handleUploadModel"
37-
:on-close="handleClose"
34+
@back="goToPreviousStep"
35+
@fetch-metadata="handleFetchMetadata"
36+
@upload="handleUploadModel"
37+
@close="handleClose"
3838
/>
3939
</div>
4040
</template>
4141

4242
<script setup lang="ts">
43-
import { computed, onMounted, ref, watch } from 'vue'
44-
45-
import { st } from '@/i18n'
4643
import UploadModelConfirmation from '@/platform/assets/components/UploadModelConfirmation.vue'
4744
import UploadModelFooter from '@/platform/assets/components/UploadModelFooter.vue'
4845
import UploadModelProgress from '@/platform/assets/components/UploadModelProgress.vue'
4946
import UploadModelUrlInput from '@/platform/assets/components/UploadModelUrlInput.vue'
50-
import { useModelTypes } from '@/platform/assets/composables/useModelTypes'
51-
import type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'
52-
import { assetService } from '@/platform/assets/services/assetService'
47+
import { useUploadModelWizard } from '@/platform/assets/composables/useUploadModelWizard'
5348
import { useDialogStore } from '@/stores/dialogStore'
5449
5550
const dialogStore = useDialogStore()
@@ -58,152 +53,35 @@ const emit = defineEmits<{
5853
'upload-success': []
5954
}>()
6055
61-
const currentStep = ref(1)
62-
const isFetchingMetadata = ref(false)
63-
const isUploading = ref(false)
64-
const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')
65-
const uploadError = ref('')
66-
67-
const wizardData = ref<{
68-
url: string
69-
metadata: AssetMetadata | null
70-
name: string
71-
tags: string[]
72-
}>({
73-
url: '',
74-
metadata: null,
75-
name: '',
76-
tags: []
77-
})
78-
79-
const selectedModelType = ref<string | undefined>(undefined)
80-
81-
const { modelTypes, fetchModelTypes } = useModelTypes()
82-
83-
// Clear error when URL changes
84-
watch(
85-
() => wizardData.value.url,
86-
() => {
87-
uploadError.value = ''
88-
}
89-
)
90-
91-
// Validation
92-
const canFetchMetadata = computed(() => {
93-
return wizardData.value.url.trim().length > 0
94-
})
95-
96-
const canUploadModel = computed(() => {
97-
return !!selectedModelType.value
98-
})
56+
const {
57+
currentStep,
58+
isFetchingMetadata,
59+
isUploading,
60+
uploadStatus,
61+
uploadError,
62+
wizardData,
63+
selectedModelType,
64+
canFetchMetadata,
65+
canUploadModel,
66+
fetchMetadata,
67+
uploadModel,
68+
goToPreviousStep
69+
} = useUploadModelWizard()
9970
10071
async function handleFetchMetadata() {
101-
if (!canFetchMetadata.value) return
102-
103-
// Validate that URL is from Civitai domain
104-
const isCivitaiUrl = (url: string): boolean => {
105-
try {
106-
const hostname = new URL(url).hostname.toLowerCase()
107-
return hostname === 'civitai.com' || hostname.endsWith('.civitai.com')
108-
} catch {
109-
return false
110-
}
111-
}
112-
113-
if (!isCivitaiUrl(wizardData.value.url)) {
114-
uploadError.value = 'Only Civitai URLs are supported'
115-
return
116-
}
117-
118-
isFetchingMetadata.value = true
119-
try {
120-
const metadata = await assetService.getAssetMetadata(wizardData.value.url)
121-
wizardData.value.metadata = metadata
122-
123-
// Pre-fill name from metadata
124-
wizardData.value.name = metadata.filename || metadata.name || ''
125-
126-
// Pre-fill model type from metadata tags if available
127-
if (metadata.tags && metadata.tags.length > 0) {
128-
wizardData.value.tags = metadata.tags
129-
// Try to detect model type from tags
130-
const typeTag = metadata.tags.find((tag) =>
131-
modelTypes.value.some((type) => type.value === tag)
132-
)
133-
if (typeTag) {
134-
selectedModelType.value = typeTag
135-
}
136-
}
137-
138-
currentStep.value = 2
139-
} catch (error) {
140-
console.error('Failed to retrieve metadata:', error)
141-
uploadError.value =
142-
error instanceof Error
143-
? error.message
144-
: st(
145-
'assetBrowser.uploadModelFailedToRetrieveMetadata',
146-
'Failed to retrieve metadata. Please check the link and try again.'
147-
)
148-
currentStep.value = 1
149-
} finally {
150-
isFetchingMetadata.value = false
151-
}
72+
await fetchMetadata()
15273
}
15374
15475
async function handleUploadModel() {
155-
if (!canUploadModel.value) return
156-
157-
isUploading.value = true
158-
uploadStatus.value = 'uploading'
159-
160-
try {
161-
const tags = selectedModelType.value
162-
? ['models', selectedModelType.value]
163-
: ['models']
164-
const filename =
165-
wizardData.value.metadata?.filename ||
166-
wizardData.value.metadata?.name ||
167-
'model'
168-
169-
await assetService.uploadAssetFromUrl({
170-
url: wizardData.value.url,
171-
name: filename,
172-
tags,
173-
user_metadata: {
174-
source: 'civitai',
175-
source_url: wizardData.value.url,
176-
model_type: selectedModelType.value
177-
}
178-
})
179-
180-
uploadStatus.value = 'success'
181-
currentStep.value = 3
76+
const success = await uploadModel()
77+
if (success) {
18278
emit('upload-success')
183-
} catch (error) {
184-
console.error('Failed to upload asset:', error)
185-
uploadStatus.value = 'error'
186-
uploadError.value =
187-
error instanceof Error ? error.message : 'Failed to upload model'
188-
currentStep.value = 3
189-
} finally {
190-
isUploading.value = false
191-
}
192-
}
193-
194-
function goToPreviousStep() {
195-
if (currentStep.value > 1) {
196-
currentStep.value = currentStep.value - 1
19779
}
19880
}
19981
20082
function handleClose() {
20183
dialogStore.closeDialog({ key: 'upload-model' })
20284
}
203-
204-
onMounted(() => {
205-
fetchModelTypes()
206-
})
20785
</script>
20886

20987
<style scoped>

src/platform/assets/components/UploadModelFooter.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
type="secondary"
77
size="md"
88
:disabled="isFetchingMetadata || isUploading"
9-
:on-click="onBack"
9+
@click="emit('back')"
1010
/>
1111
<span v-else />
1212

@@ -16,7 +16,7 @@
1616
type="primary"
1717
size="md"
1818
:disabled="!canFetchMetadata || isFetchingMetadata"
19-
:on-click="onFetchMetadata"
19+
@click="emit('fetchMetadata')"
2020
>
2121
<template #icon>
2222
<i
@@ -31,7 +31,7 @@
3131
type="primary"
3232
size="md"
3333
:disabled="!canUploadModel || isUploading"
34-
:on-click="onUpload"
34+
@click="emit('upload')"
3535
>
3636
<template #icon>
3737
<i
@@ -45,7 +45,7 @@
4545
:label="$t('assetBrowser.finish')"
4646
type="primary"
4747
size="md"
48-
:on-click="onClose"
48+
@click="emit('close')"
4949
/>
5050
</div>
5151
</template>
@@ -61,9 +61,12 @@ defineProps<{
6161
canFetchMetadata: boolean
6262
canUploadModel: boolean
6363
uploadStatus: 'idle' | 'uploading' | 'success' | 'error'
64-
onBack: () => void
65-
onFetchMetadata: () => void
66-
onUpload: () => void
67-
onClose: () => void
64+
}>()
65+
66+
const emit = defineEmits<{
67+
(e: 'back'): void
68+
(e: 'fetchMetadata'): void
69+
(e: 'upload'): void
70+
(e: 'close'): void
6871
}>()
6972
</script>

src/platform/assets/components/UploadModelProgress.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
</p>
2727
</div>
2828

29-
<div
30-
class="flex flex-row items-start p-8 bg-node-component-surface rounded-lg"
31-
>
29+
<div class="flex flex-row items-start p-8 bg-neutral-800 rounded-lg">
3230
<div class="flex flex-col justify-center items-start gap-1 flex-1">
3331
<p class="text-sm m-0">
3432
{{ metadata?.name || metadata?.filename }}

src/platform/assets/composables/useModelTypes.ts

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ref } from 'vue'
2-
import { createSharedComposable } from '@vueuse/core'
1+
import { createSharedComposable, useAsyncState } from '@vueuse/core'
32

43
import { api } from '@/scripts/api'
54

@@ -43,47 +42,27 @@ interface ModelTypeOption {
4342
* Uses shared state to ensure data is only fetched once
4443
*/
4544
export const useModelTypes = createSharedComposable(() => {
46-
const modelTypes = ref<ModelTypeOption[]>([])
47-
const isLoading = ref(false)
48-
const error = ref<string | null>(null)
49-
let fetchPromise: Promise<void> | null = null
50-
51-
/**
52-
* Fetch model types from the API (only fetches once, subsequent calls reuse the same promise)
53-
*/
54-
async function fetchModelTypes() {
55-
// If already loaded, return immediately
56-
if (modelTypes.value.length > 0) {
57-
return
58-
}
59-
60-
// If currently loading, return the existing promise
61-
if (fetchPromise) {
62-
return fetchPromise
63-
}
64-
65-
isLoading.value = true
66-
error.value = null
67-
68-
fetchPromise = (async () => {
69-
try {
70-
const response = await api.getModelFolders()
71-
modelTypes.value = response.map((folder) => ({
72-
name: formatDisplayName(folder.name),
73-
value: folder.name
74-
}))
75-
} catch (err) {
76-
error.value =
77-
err instanceof Error ? err.message : 'Failed to fetch model types'
45+
const {
46+
state: modelTypes,
47+
isLoading,
48+
error,
49+
execute: fetchModelTypes
50+
} = useAsyncState(
51+
async (): Promise<ModelTypeOption[]> => {
52+
const response = await api.getModelFolders()
53+
return response.map((folder) => ({
54+
name: formatDisplayName(folder.name),
55+
value: folder.name
56+
}))
57+
},
58+
[] as ModelTypeOption[],
59+
{
60+
immediate: false,
61+
onError: (err) => {
7862
console.error('Failed to fetch model types:', err)
79-
} finally {
80-
isLoading.value = false
81-
fetchPromise = null
8263
}
83-
})()
84-
85-
return fetchPromise
86-
}
64+
}
65+
)
8766

8867
return {
8968
modelTypes,

0 commit comments

Comments
 (0)