feat: support download multiple actual files (#196)
This commit is contained in:
@@ -69,8 +69,8 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
models: list[dict] = []
|
models: list[dict] = []
|
||||||
|
|
||||||
for version in model_versions:
|
for version in model_versions:
|
||||||
model_files: list[dict] = version.get("files", [])
|
version_files: list[dict] = version.get("files", [])
|
||||||
model_files = utils.filter_with(model_files, {"type": "Model"})
|
model_files = utils.filter_with(version_files, {"type": "Model"})
|
||||||
|
|
||||||
shortname = version.get("name", None) if len(model_files) > 0 else None
|
shortname = version.get("name", None) if len(model_files) > 0 else None
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
description_parts.append("")
|
description_parts.append("")
|
||||||
|
|
||||||
model = {
|
model = {
|
||||||
"id": file.get("id"),
|
"id": version.get("id"),
|
||||||
"shortname": shortname or basename,
|
"shortname": shortname or basename,
|
||||||
"basename": basename,
|
"basename": basename,
|
||||||
"extension": extension,
|
"extension": extension,
|
||||||
@@ -122,6 +122,7 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
"downloadPlatform": "civitai",
|
"downloadPlatform": "civitai",
|
||||||
"downloadUrl": file.get("downloadUrl"),
|
"downloadUrl": file.get("downloadUrl"),
|
||||||
"hashes": file.get("hashes"),
|
"hashes": file.get("hashes"),
|
||||||
|
"files": version_files if len(version_files) > 1 else None,
|
||||||
}
|
}
|
||||||
models.append(model)
|
models.append(model)
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,20 @@
|
|||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<ModelContent
|
<ModelContent
|
||||||
v-if="currentModel"
|
v-if="currentModel"
|
||||||
:key="currentModel.id"
|
:key="`${currentModel.id}-${currentModel.currentFileId}`"
|
||||||
:model="currentModel"
|
:model="currentModel"
|
||||||
:editable="true"
|
:editable="true"
|
||||||
@submit="createDownTask"
|
@submit="createDownTask"
|
||||||
>
|
>
|
||||||
<template #action>
|
<template #action>
|
||||||
|
<div v-if="currentModel.files" class="flex-1">
|
||||||
|
<ResponseSelect
|
||||||
|
:model-value="currentModel.currentFileId"
|
||||||
|
:items="currentModel.selectionFiles"
|
||||||
|
:type="isMobile ? 'drop' : 'button'"
|
||||||
|
>
|
||||||
|
</ResponseSelect>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-download"
|
icon="pi pi-download"
|
||||||
:label="$t('download')"
|
:label="$t('download')"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
></ModelPreview>
|
></ModelPreview>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 overflow-hidden">
|
<div class="flex flex-col gap-4 overflow-hidden">
|
||||||
<div class="flex items-center justify-end gap-4">
|
<div class="flex h-10 items-center justify-end gap-4">
|
||||||
<slot name="action" :metadata="formInstance.metadata.value"></slot>
|
<slot name="action" :metadata="formInstance.metadata.value"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ import { useLoading } from 'hooks/loading'
|
|||||||
import { request } from 'hooks/request'
|
import { request } from 'hooks/request'
|
||||||
import { defineStore } from 'hooks/store'
|
import { defineStore } from 'hooks/store'
|
||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
|
import { upperFirst } from 'lodash'
|
||||||
import { api } from 'scripts/comfyAPI'
|
import { api } from 'scripts/comfyAPI'
|
||||||
import {
|
import {
|
||||||
BaseModel,
|
|
||||||
DownloadTask,
|
DownloadTask,
|
||||||
DownloadTaskOptions,
|
DownloadTaskOptions,
|
||||||
SelectOptions,
|
SelectOptions,
|
||||||
VersionModel,
|
VersionModel,
|
||||||
|
VersionModelFile,
|
||||||
} from 'types/typings'
|
} from 'types/typings'
|
||||||
import { bytesToSize } from 'utils/common'
|
import { bytesToSize } from 'utils/common'
|
||||||
import { onBeforeMount, onMounted, ref, watch } from 'vue'
|
import { onBeforeMount, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import yaml from 'yaml'
|
||||||
|
|
||||||
export const useDownload = defineStore('download', (store) => {
|
export const useDownload = defineStore('download', (store) => {
|
||||||
const { toast, confirm, wrapperToastError } = useToast()
|
const { toast, confirm, wrapperToastError } = useToast()
|
||||||
@@ -162,12 +164,60 @@ declare module 'hooks/store' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WithSelection<T> = SelectOptions & { item: T }
|
||||||
|
|
||||||
|
type FileSelectionVersionModel = VersionModel & {
|
||||||
|
currentFileId?: number
|
||||||
|
selectionFiles?: WithSelection<VersionModelFile>[]
|
||||||
|
}
|
||||||
|
|
||||||
export const useModelSearch = () => {
|
export const useModelSearch = () => {
|
||||||
const loading = useLoading()
|
const loading = useLoading()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const data = ref<(SelectOptions & { item: VersionModel })[]>([])
|
const data = ref<WithSelection<FileSelectionVersionModel>[]>([])
|
||||||
const current = ref<string | number>()
|
const current = ref<string | number>()
|
||||||
const currentModel = ref<BaseModel>()
|
const currentModel = ref<FileSelectionVersionModel>()
|
||||||
|
|
||||||
|
const genFileSelectionItem = (
|
||||||
|
item: VersionModel,
|
||||||
|
): FileSelectionVersionModel => {
|
||||||
|
const fileSelectionItem: FileSelectionVersionModel = { ...item }
|
||||||
|
fileSelectionItem.selectionFiles = fileSelectionItem.files
|
||||||
|
?.sort((file) => (file.type === 'Model' ? -1 : 1))
|
||||||
|
.map((file) => {
|
||||||
|
const parts = file.name.split('.')
|
||||||
|
const extension = `.${parts.pop()}`
|
||||||
|
const basename = parts.join('.')
|
||||||
|
|
||||||
|
const regexp = /---\n([\s\S]*?)\n---/
|
||||||
|
const yamlMetadataMatch = item.description.match(regexp)
|
||||||
|
const yamlMetadata = yaml.parse(yamlMetadataMatch?.[1] || '')
|
||||||
|
yamlMetadata.hashes = file.hashes
|
||||||
|
yamlMetadata.metadata = file.metadata
|
||||||
|
const yamlContent = `---\n${yaml.stringify(yamlMetadata)}---`
|
||||||
|
const description = item.description.replace(regexp, yamlContent)
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: file.type === 'Model' ? upperFirst(item.type) : file.type,
|
||||||
|
value: file.id,
|
||||||
|
item: file,
|
||||||
|
command() {
|
||||||
|
if (currentModel.value) {
|
||||||
|
currentModel.value.basename = basename
|
||||||
|
currentModel.value.extension = extension
|
||||||
|
currentModel.value.sizeBytes = file.sizeKB * 1024
|
||||||
|
currentModel.value.metadata = file.metadata
|
||||||
|
currentModel.value.downloadUrl = file.downloadUrl
|
||||||
|
currentModel.value.hashes = file.hashes
|
||||||
|
currentModel.value.description = description
|
||||||
|
currentModel.value.currentFileId = file.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
fileSelectionItem.currentFileId = item.files?.[0]?.id
|
||||||
|
return fileSelectionItem
|
||||||
|
}
|
||||||
|
|
||||||
const handleSearchByUrl = async (url: string) => {
|
const handleSearchByUrl = async (url: string) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@@ -177,14 +227,17 @@ export const useModelSearch = () => {
|
|||||||
loading.show()
|
loading.show()
|
||||||
return request(`/model-info?model-page=${encodeURIComponent(url)}`, {})
|
return request(`/model-info?model-page=${encodeURIComponent(url)}`, {})
|
||||||
.then((resData: VersionModel[]) => {
|
.then((resData: VersionModel[]) => {
|
||||||
data.value = resData.map((item) => ({
|
data.value = resData.map((item) => {
|
||||||
label: item.shortname,
|
const resolvedItem = genFileSelectionItem(item)
|
||||||
value: item.id,
|
return {
|
||||||
item,
|
label: item.shortname,
|
||||||
command() {
|
value: item.id,
|
||||||
current.value = item.id
|
item: resolvedItem,
|
||||||
},
|
command() {
|
||||||
}))
|
current.value = item.id
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
current.value = data.value[0]?.value
|
current.value = data.value[0]?.value
|
||||||
currentModel.value = data.value[0]?.item
|
currentModel.value = data.value[0]?.item
|
||||||
|
|
||||||
|
|||||||
11
src/types/typings.d.ts
vendored
11
src/types/typings.d.ts
vendored
@@ -22,11 +22,22 @@ export interface Model extends BaseModel {
|
|||||||
children?: Model[]
|
children?: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VersionModelFile {
|
||||||
|
id: number
|
||||||
|
sizeKB: number
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
metadata: Record<string, string>
|
||||||
|
hashes: Record<string, string>
|
||||||
|
downloadUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface VersionModel extends BaseModel {
|
export interface VersionModel extends BaseModel {
|
||||||
shortname: string
|
shortname: string
|
||||||
downloadPlatform: string
|
downloadPlatform: string
|
||||||
downloadUrl: string
|
downloadUrl: string
|
||||||
hashes?: Record<string, string>
|
hashes?: Record<string, string>
|
||||||
|
files?: VersionModelFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WithResolved<T> = Omit<T, 'preview'> & {
|
export type WithResolved<T> = Omit<T, 'preview'> & {
|
||||||
|
|||||||
Reference in New Issue
Block a user