mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
Fetch model metadata for Civitai models embedded in workflows (#2994)
This commit is contained in:
95
src/composables/useCivitaiModel.ts
Normal file
95
src/composables/useCivitaiModel.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { useAsyncState } from '@vueuse/core'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
type ModelType =
|
||||||
|
| 'Checkpoint'
|
||||||
|
| 'TextualInversion'
|
||||||
|
| 'Hypernetwork'
|
||||||
|
| 'AestheticGradient'
|
||||||
|
| 'LORA'
|
||||||
|
| 'Controlnet'
|
||||||
|
| 'Poses'
|
||||||
|
|
||||||
|
interface CivitaiFileMetadata {
|
||||||
|
fp?: 'fp16' | 'fp32'
|
||||||
|
size?: 'full' | 'pruned'
|
||||||
|
format?: 'SafeTensor' | 'PickleTensor' | 'Other'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CivitaiModelFile {
|
||||||
|
name: string
|
||||||
|
id: number
|
||||||
|
sizeKB: number
|
||||||
|
type: string
|
||||||
|
downloadUrl: string
|
||||||
|
metadata: CivitaiFileMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CivitaiModel {
|
||||||
|
name: string
|
||||||
|
type: ModelType
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CivitaiModelVersionResponse {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
model: CivitaiModel
|
||||||
|
modelId: number
|
||||||
|
files: CivitaiModelFile[]
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable to manage Civitai model
|
||||||
|
* @param url - The URL of the Civitai model, where the model ID is the last part of the URL's pathname
|
||||||
|
* @see https://developer.civitai.com/docs/api/public-rest
|
||||||
|
* @example
|
||||||
|
* const { fileSize, isLoading, error, modelData } =
|
||||||
|
* useCivitaiModel('https://civitai.com/api/download/models/16576?type=Model&format=SafeTensor&size=full&fp=fp16')
|
||||||
|
*/
|
||||||
|
export function useCivitaiModel(url: string) {
|
||||||
|
const createModelVersionUrl = (modelId: string): string =>
|
||||||
|
`https://civitai.com/api/v1/model-versions/${modelId}`
|
||||||
|
|
||||||
|
const extractModelIdFromUrl = (): string | null => {
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
return urlObj.pathname.split('/').pop() || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchModelData =
|
||||||
|
async (): Promise<CivitaiModelVersionResponse | null> => {
|
||||||
|
const modelId = extractModelIdFromUrl()
|
||||||
|
if (!modelId) return null
|
||||||
|
|
||||||
|
const apiUrl = createModelVersionUrl(modelId)
|
||||||
|
const res = await fetch(apiUrl)
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
const findMatchingFileSize = (): number | null => {
|
||||||
|
const matchingFile = modelData.value?.files?.find(
|
||||||
|
(file) => file.downloadUrl && url.startsWith(file.downloadUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
return matchingFile?.sizeKB ? matchingFile.sizeKB << 10 : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
state: modelData,
|
||||||
|
isLoading,
|
||||||
|
error
|
||||||
|
} = useAsyncState(fetchModelData, null, {
|
||||||
|
immediate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileSize = computed(() =>
|
||||||
|
!isLoading.value ? findMatchingFileSize() : null
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileSize,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
|
import { whenever } from '@vueuse/core'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useCivitaiModel } from '@/composables/useCivitaiModel'
|
||||||
|
import { isCivitaiModelUrl } from '@/utils/formatUtil'
|
||||||
|
|
||||||
export function useDownload(url: string, fileName?: string) {
|
export function useDownload(url: string, fileName?: string) {
|
||||||
const fileSize = ref<number | null>(null)
|
const fileSize = ref<number | null>(null)
|
||||||
|
|
||||||
const fetchFileSize = async (): Promise<number | null> => {
|
const setFileSize = (size: number) => {
|
||||||
|
fileSize.value = size
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchFileSize = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { method: 'HEAD' })
|
const response = await fetch(url, { method: 'HEAD' })
|
||||||
if (!response.ok) throw new Error('Failed to fetch file size')
|
if (!response.ok) throw new Error('Failed to fetch file size')
|
||||||
|
|
||||||
const size = response.headers.get('content-length')
|
const size = response.headers.get('content-length')
|
||||||
if (size) {
|
if (size) {
|
||||||
return parseInt(size)
|
setFileSize(parseInt(size))
|
||||||
} else {
|
} else {
|
||||||
console.error('"content-length" header not found')
|
console.error('"content-length" header not found')
|
||||||
return null
|
return null
|
||||||
@@ -33,8 +41,15 @@ export function useDownload(url: string, fileName?: string) {
|
|||||||
link.click()
|
link.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
fileSize.value = await fetchFileSize()
|
if (isCivitaiModelUrl(url)) {
|
||||||
|
const { fileSize: civitaiSize, error: civitaiErr } = useCivitaiModel(url)
|
||||||
|
whenever(civitaiSize, setFileSize)
|
||||||
|
// Try falling back to normal fetch if using Civitai API fails
|
||||||
|
whenever(civitaiErr, fetchFileSize, { once: true })
|
||||||
|
} else {
|
||||||
|
fetchFileSize()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -343,3 +343,25 @@ export const generateUUID = (): string => {
|
|||||||
*/
|
*/
|
||||||
export const formatNumber = (num?: number): string =>
|
export const formatNumber = (num?: number): string =>
|
||||||
num?.toLocaleString() ?? 'N/A'
|
num?.toLocaleString() ?? 'N/A'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a URL is a Civitai model URL
|
||||||
|
* @example
|
||||||
|
* isCivitaiModelUrl('https://civitai.com/api/download/models/1234567890') // true
|
||||||
|
* isCivitaiModelUrl('https://civitai.com/api/v1/models/1234567890') // true
|
||||||
|
* isCivitaiModelUrl('https://civitai.com/api/v1/models-versions/15342') // true
|
||||||
|
* isCivitaiModelUrl('https://example.com/model.safetensors') // false
|
||||||
|
*/
|
||||||
|
export const isCivitaiModelUrl = (url: string): boolean => {
|
||||||
|
if (!isValidUrl(url)) return false
|
||||||
|
if (!url.includes('civitai.com')) return false
|
||||||
|
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const pathname = urlObj.pathname
|
||||||
|
|
||||||
|
return (
|
||||||
|
/^\/api\/download\/models\/(\d+)$/.test(pathname) ||
|
||||||
|
/^\/api\/v1\/models\/(\d+)$/.test(pathname) ||
|
||||||
|
/^\/api\/v1\/models-versions\/(\d+)$/.test(pathname)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user