From 17ceb75dce1d12faa19b720e2962d67c22fa5396 Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Wed, 5 Nov 2025 14:46:36 -0800 Subject: [PATCH] WIP --- src/locales/en/main.json | 7 ++ .../assets/components/AssetBrowserModal.vue | 10 ++- .../assets/components/UploadModelDialog.vue | 74 +++++++++++++++++++ .../components/UploadModelDialogHeader.vue | 10 +++ src/platform/assets/services/assetService.ts | 71 +++++++++++++++++- 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/platform/assets/components/UploadModelDialog.vue create mode 100644 src/platform/assets/components/UploadModelDialogHeader.vue diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d9f04ca4c..4c936b473 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -1984,6 +1984,13 @@ "noModelsInFolder": "No {type} available in this folder", "searchAssetsPlaceholder": "Type to search...", "uploadModel": "Upload model", + "uploadModelFromCivitai": "Upload a model from Civitai", + "uploadModelDescription1": "Paste a Civitai model download link to add it to your library.", + "uploadModelDescription2": "Only links from https://civitai.com are supported at the moment", + "uploadModelDescription3": "Max file size: 1 GB", + "civitaiLinkLabel": "Civitai model download link", + "civitaiLinkPlaceholder": "Paste link here", + "civitaiLinkExample": "Example: https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor", "allModels": "All Models", "allCategory": "All {category}", "unknown": "Unknown", diff --git a/src/platform/assets/components/AssetBrowserModal.vue b/src/platform/assets/components/AssetBrowserModal.vue index 2f0469e38..d5f40c70b 100644 --- a/src/platform/assets/components/AssetBrowserModal.vue +++ b/src/platform/assets/components/AssetBrowserModal.vue @@ -73,11 +73,14 @@ import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue' import { useFeatureFlags } from '@/composables/useFeatureFlags' import AssetFilterBar from '@/platform/assets/components/AssetFilterBar.vue' import AssetGrid from '@/platform/assets/components/AssetGrid.vue' +import UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue' +import UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue' import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' import { useAssetBrowser } from '@/platform/assets/composables/useAssetBrowser' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' import { assetService } from '@/platform/assets/services/assetService' import { formatCategoryLabel } from '@/platform/assets/utils/categoryLabel' +import { useDialogStore } from '@/stores/dialogStore' import { useModelToNodeStore } from '@/stores/modelToNodeStore' import { OnCloseKey } from '@/types/widgetTypes' @@ -92,6 +95,7 @@ const props = defineProps<{ }>() const { t } = useI18n() +const dialogStore = useDialogStore() const emit = defineEmits<{ 'asset-select': [asset: AssetDisplayItem] @@ -189,6 +193,10 @@ const { flags } = useFeatureFlags() const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled) function handleUploadClick() { - // Will be implemented in the future commit + dialogStore.showDialog({ + key: 'upload-model', + headerComponent: UploadModelDialogHeader, + component: UploadModelDialog + }) } diff --git a/src/platform/assets/components/UploadModelDialog.vue b/src/platform/assets/components/UploadModelDialog.vue new file mode 100644 index 000000000..f485f2c6d --- /dev/null +++ b/src/platform/assets/components/UploadModelDialog.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/platform/assets/components/UploadModelDialogHeader.vue b/src/platform/assets/components/UploadModelDialogHeader.vue new file mode 100644 index 000000000..b0da60c43 --- /dev/null +++ b/src/platform/assets/components/UploadModelDialogHeader.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/platform/assets/services/assetService.ts b/src/platform/assets/services/assetService.ts index 85023b29b..9f4613f42 100644 --- a/src/platform/assets/services/assetService.ts +++ b/src/platform/assets/services/assetService.ts @@ -249,6 +249,73 @@ function createAssetService() { } } + /** + * Retrieves metadata from a download URL without downloading the file + * + * @param url - Download URL to retrieve metadata from (will be URL-encoded) + * @returns Promise with metadata including content_length, final_url, filename, etc. + * @throws Error if metadata retrieval fails + */ + async function getAssetMetadata(url: string): Promise<{ + content_length: number + final_url: string + content_type?: string + filename?: string + name?: string + tags?: string[] + }> { + const encodedUrl = encodeURIComponent(url) + const res = await api.fetchApi( + `${ASSETS_ENDPOINT}/metadata?url=${encodedUrl}` + ) + + if (!res.ok) { + const errorText = await res.text().catch(() => 'Unknown error') + throw new Error( + `Failed to retrieve metadata: Server returned ${res.status}. ${errorText}` + ) + } + + return await res.json() + } + + /** + * Uploads an asset by providing a URL to download from + * + * @param params - Upload parameters + * @param params.url - HTTP/HTTPS URL to download from + * @param params.name - Display name (determines extension) + * @param params.tags - Optional freeform tags + * @param params.user_metadata - Optional custom metadata object + * @param params.preview_id - Optional UUID for preview asset + * @returns Promise - Asset object with created_new flag + * @throws Error if upload fails + */ + async function uploadAssetFromUrl(params: { + url: string + name: string + tags?: string[] + user_metadata?: Record + preview_id?: string + }): Promise { + const res = await api.fetchApi(ASSETS_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(params) + }) + + if (!res.ok) { + const errorText = await res.text().catch(() => 'Unknown error') + throw new Error( + `Failed to upload asset: Server returned ${res.status}. ${errorText}` + ) + } + + return await res.json() + } + return { getAssetModelFolders, getAssetModels, @@ -256,7 +323,9 @@ function createAssetService() { getAssetsForNodeType, getAssetDetails, getAssetsByTag, - deleteAsset + deleteAsset, + getAssetMetadata, + uploadAssetFromUrl } }