Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ba80fab2e | ||
|
|
b9e637049a | ||
|
|
bfccc6f04f | ||
|
|
89c249542a | ||
|
|
136bc0ecd5 | ||
|
|
8653af1f14 | ||
|
|
354b5c840a | ||
|
|
be383ac6e1 | ||
|
|
c2406a1fd1 |
@@ -114,7 +114,8 @@ async def create_model(request):
|
|||||||
- downloadUrl: download url.
|
- downloadUrl: download url.
|
||||||
- hash: a JSON string containing the hash value of the downloaded model.
|
- hash: a JSON string containing the hash value of the downloaded model.
|
||||||
"""
|
"""
|
||||||
task_data = await request.json()
|
task_data = await request.post()
|
||||||
|
task_data = dict(task_data)
|
||||||
try:
|
try:
|
||||||
task_id = await services.create_model_download_task(task_data, request)
|
task_id = await services.create_model_download_task(task_data, request)
|
||||||
return web.json_response({"success": True, "data": {"taskId": task_id}})
|
return web.json_response({"success": True, "data": {"taskId": task_id}})
|
||||||
@@ -130,7 +131,7 @@ async def list_model_types(request):
|
|||||||
Scan all models and read their information.
|
Scan all models and read their information.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = utils.resolve_model_base_paths(request)
|
result = utils.resolve_model_base_paths()
|
||||||
return web.json_response({"success": True, "data": result})
|
return web.json_response({"success": True, "data": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Read models failed: {str(e)}"
|
error_msg = f"Read models failed: {str(e)}"
|
||||||
@@ -186,7 +187,8 @@ async def update_model(request):
|
|||||||
index = int(request.match_info.get("index", None))
|
index = int(request.match_info.get("index", None))
|
||||||
filename = request.match_info.get("filename", None)
|
filename = request.match_info.get("filename", None)
|
||||||
|
|
||||||
model_data: dict = await request.json()
|
model_data = await request.post()
|
||||||
|
model_data = dict(model_data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model_path = utils.get_valid_full_path(model_type, index, filename)
|
model_path = utils.get_valid_full_path(model_type, index, filename)
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ setting_key = {
|
|||||||
"max_task_count": "ModelManager.Download.MaxTaskCount",
|
"max_task_count": "ModelManager.Download.MaxTaskCount",
|
||||||
},
|
},
|
||||||
"scan": {
|
"scan": {
|
||||||
"include_hidden_files": "ModelManager.Scan.IncludeHiddenFiles",
|
"include_hidden_files": "ModelManager.Scan.IncludeHiddenFiles"
|
||||||
"exclude_scan_types": "ModelManager.Scan.excludeScanTypes",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,8 +180,8 @@ async def create_model_download_task(task_data: dict, request):
|
|||||||
raise RuntimeError(f"Task {task_id} already exists")
|
raise RuntimeError(f"Task {task_id} already exists")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
preview_url = task_data.pop("preview", None)
|
previewFile = task_data.pop("previewFile", None)
|
||||||
utils.save_model_preview_image(task_path, preview_url)
|
utils.save_model_preview_image(task_path, previewFile)
|
||||||
set_task_content(task_id, task_data)
|
set_task_content(task_id, task_data)
|
||||||
task_status = TaskStatus(
|
task_status = TaskStatus(
|
||||||
taskId=task_id,
|
taskId=task_id,
|
||||||
@@ -361,9 +361,7 @@ async def download_model_file(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code not in (200, 206):
|
if response.status_code not in (200, 206):
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Failed to download {task_content.fullname}, status code: {response.status_code}")
|
||||||
f"Failed to download {task_content.fullname}, status code: {response.status_code}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Some models require logging in before they can be downloaded.
|
# Some models require logging in before they can be downloaded.
|
||||||
# If no token is carried, it will be redirected to the login page.
|
# If no token is carried, it will be redirected to the login page.
|
||||||
@@ -376,9 +374,7 @@ async def download_model_file(
|
|||||||
# If it cannot be downloaded, a redirect will definitely occur.
|
# If it cannot be downloaded, a redirect will definitely occur.
|
||||||
# Maybe consider getting the redirect url from response.history to make a judgment.
|
# Maybe consider getting the redirect url from response.history to make a judgment.
|
||||||
# Here we also need to consider how different websites are processed.
|
# Here we also need to consider how different websites are processed.
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"{task_content.fullname} needs to be logged in to download. Please set the API-Key first.")
|
||||||
f"{task_content.fullname} needs to be logged in to download. Please set the API-Key first."
|
|
||||||
)
|
|
||||||
|
|
||||||
# When parsing model information from HuggingFace API,
|
# When parsing model information from HuggingFace API,
|
||||||
# the file size was not found and needs to be obtained from the response header.
|
# the file size was not found and needs to be obtained from the response header.
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ def update_model(model_path: str, model_data: dict):
|
|||||||
|
|
||||||
if "previewFile" in model_data:
|
if "previewFile" in model_data:
|
||||||
previewFile = model_data["previewFile"]
|
previewFile = model_data["previewFile"]
|
||||||
utils.save_model_preview_image(model_path, previewFile)
|
if type(previewFile) is str and previewFile == "undefined":
|
||||||
|
utils.remove_model_preview_image(model_path)
|
||||||
|
else:
|
||||||
|
utils.save_model_preview_image(model_path, previewFile)
|
||||||
|
|
||||||
if "description" in model_data:
|
if "description" in model_data:
|
||||||
description = model_data["description"]
|
description = model_data["description"]
|
||||||
|
|||||||
51
py/utils.py
51
py/utils.py
@@ -116,13 +116,10 @@ def download_web_distribution(version: str):
|
|||||||
print_error(f"An unexpected error occurred: {e}")
|
print_error(f"An unexpected error occurred: {e}")
|
||||||
|
|
||||||
|
|
||||||
def resolve_model_base_paths(request):
|
def resolve_model_base_paths():
|
||||||
folders = list(folder_paths.folder_names_and_paths.keys())
|
folders = list(folder_paths.folder_names_and_paths.keys())
|
||||||
model_base_paths = {}
|
model_base_paths = {}
|
||||||
folder_black_list = ["configs", "custom_nodes"]
|
folder_black_list = ["configs", "custom_nodes"]
|
||||||
custom_folders = get_setting_value(request, "scan.exclude_scan_types", "")
|
|
||||||
custom_black_list = [f.strip() for f in custom_folders.split(",") if f.strip()]
|
|
||||||
folder_black_list.extend(custom_black_list)
|
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
if folder in folder_black_list:
|
if folder in folder_black_list:
|
||||||
continue
|
continue
|
||||||
@@ -252,19 +249,45 @@ from PIL import Image
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
def save_model_preview_image(model_path: str, image_url: str):
|
def remove_model_preview_image(model_path: str):
|
||||||
try:
|
basename = os.path.splitext(model_path)[0]
|
||||||
image_response = requests.get(image_url)
|
preview_path = f"{basename}.webp"
|
||||||
image_response.raise_for_status()
|
if os.path.exists(preview_path):
|
||||||
|
os.remove(preview_path)
|
||||||
|
|
||||||
basename = os.path.splitext(model_path)[0]
|
|
||||||
preview_path = f"{basename}.webp"
|
def save_model_preview_image(model_path: str, image_file_or_url: Any):
|
||||||
image = Image.open(BytesIO(image_response.content))
|
basename = os.path.splitext(model_path)[0]
|
||||||
|
preview_path = f"{basename}.webp"
|
||||||
|
|
||||||
|
# Download image file if it is url
|
||||||
|
if type(image_file_or_url) is str:
|
||||||
|
image_url = image_file_or_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_response = requests.get(image_url)
|
||||||
|
image_response.raise_for_status()
|
||||||
|
|
||||||
|
image = Image.open(BytesIO(image_response.content))
|
||||||
|
image.save(preview_path, "WEBP")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print_error(f"Failed to download image: {e}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Assert image as file
|
||||||
|
image_file = image_file_or_url
|
||||||
|
|
||||||
|
if not isinstance(image_file, web.FileField):
|
||||||
|
raise RuntimeError("Invalid image file")
|
||||||
|
|
||||||
|
content_type: str = image_file.content_type
|
||||||
|
if not content_type.startswith("image/"):
|
||||||
|
raise RuntimeError(f"FileTypeError: expected image, got {content_type}")
|
||||||
|
|
||||||
|
image = Image.open(image_file.file)
|
||||||
image.save(preview_path, "WEBP")
|
image.save(preview_path, "WEBP")
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print_error(f"Failed to download image: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_model_all_descriptions(model_path: str):
|
def get_model_all_descriptions(model_path: str):
|
||||||
base_dirname = os.path.dirname(model_path)
|
base_dirname = os.path.dirname(model_path)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-model-manager"
|
name = "comfyui-model-manager"
|
||||||
description = "Manage models: browsing, download and delete."
|
description = "Manage models: browsing, download and delete."
|
||||||
version = "2.2.0"
|
version = "2.2.3"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
dependencies = ["markdownify"]
|
dependencies = ["markdownify"]
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ import { useLoading } from 'hooks/loading'
|
|||||||
import { request } from 'hooks/request'
|
import { request } from 'hooks/request'
|
||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import { VersionModel } from 'types/typings'
|
import { VersionModel, WithResolved } from 'types/typings'
|
||||||
|
import { previewUrlToFile } from 'utils/common'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const { isMobile } = useConfig()
|
const { isMobile } = useConfig()
|
||||||
@@ -87,15 +88,52 @@ const searchModelsByUrl = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDownTask = async (data: VersionModel) => {
|
const createDownTask = async (data: WithResolved<VersionModel>) => {
|
||||||
loading.show()
|
loading.show()
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
for (const key in data) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
let value = data[key]
|
||||||
|
|
||||||
|
// set preview file
|
||||||
|
if (key === 'preview') {
|
||||||
|
if (value) {
|
||||||
|
const previewFile = await previewUrlToFile(value).catch(() => {
|
||||||
|
loading.hide()
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'Failed to download preview',
|
||||||
|
life: 5000,
|
||||||
|
})
|
||||||
|
throw new Error('Failed to download preview')
|
||||||
|
})
|
||||||
|
formData.append('previewFile', previewFile)
|
||||||
|
} else {
|
||||||
|
formData.append('previewFile', value)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
value = JSON.stringify(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
value = value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await request('/model', {
|
await request('/model', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: formData,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dialog.close({ key: 'model-manager-create-task' })
|
dialog.close()
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
toast.add({
|
toast.add({
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const dialog = useDialog()
|
|||||||
|
|
||||||
const openCreateTask = () => {
|
const openCreateTask = () => {
|
||||||
dialog.open({
|
dialog.open({
|
||||||
key: 'model-manager-create-task',
|
key: `model-manager-create-task-${Date.now()}`,
|
||||||
title: t('parseModelUrl'),
|
title: t('parseModelUrl'),
|
||||||
content: DialogCreateTask,
|
content: DialogCreateTask,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,11 +68,12 @@ import ModelCard from 'components/ModelCard.vue'
|
|||||||
import ResponseInput from 'components/ResponseInput.vue'
|
import ResponseInput from 'components/ResponseInput.vue'
|
||||||
import ResponseScroll from 'components/ResponseScroll.vue'
|
import ResponseScroll from 'components/ResponseScroll.vue'
|
||||||
import ResponseSelect from 'components/ResponseSelect.vue'
|
import ResponseSelect from 'components/ResponseSelect.vue'
|
||||||
import { useConfig } from 'hooks/config'
|
import { configSetting, useConfig } from 'hooks/config'
|
||||||
import { useContainerQueries } from 'hooks/container'
|
import { useContainerQueries } from 'hooks/container'
|
||||||
import { useModels } from 'hooks/model'
|
import { useModels } from 'hooks/model'
|
||||||
import { defineResizeCallback } from 'hooks/resize'
|
import { defineResizeCallback } from 'hooks/resize'
|
||||||
import { chunk } from 'lodash'
|
import { chunk } from 'lodash'
|
||||||
|
import { app } from 'scripts/comfyAPI'
|
||||||
import { Model } from 'types/typings'
|
import { Model } from 'types/typings'
|
||||||
import { genModelKey } from 'utils/model'
|
import { genModelKey } from 'utils/model'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
@@ -89,7 +90,20 @@ const searchContent = ref<string>()
|
|||||||
|
|
||||||
const currentType = ref('all')
|
const currentType = ref('all')
|
||||||
const typeOptions = computed(() => {
|
const typeOptions = computed(() => {
|
||||||
return ['all', ...Object.keys(folders.value)].map((type) => {
|
const excludeScanTypes = app.ui?.settings.getSettingValue<string>(
|
||||||
|
configSetting.excludeScanTypes,
|
||||||
|
)
|
||||||
|
const customBlackList =
|
||||||
|
excludeScanTypes
|
||||||
|
?.split(',')
|
||||||
|
.map((type) => type.trim())
|
||||||
|
.filter(Boolean) ?? []
|
||||||
|
return [
|
||||||
|
'all',
|
||||||
|
...Object.keys(folders.value).filter(
|
||||||
|
(folder) => !customBlackList.includes(folder),
|
||||||
|
),
|
||||||
|
].map((type) => {
|
||||||
return {
|
return {
|
||||||
label: type,
|
label: type,
|
||||||
value: type,
|
value: type,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import ResponseScroll from 'components/ResponseScroll.vue'
|
|||||||
import { useModelNodeAction, useModels } from 'hooks/model'
|
import { useModelNodeAction, useModels } from 'hooks/model'
|
||||||
import { useRequest } from 'hooks/request'
|
import { useRequest } from 'hooks/request'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import { BaseModel, Model } from 'types/typings'
|
import { BaseModel, Model, WithResolved } from 'types/typings'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -72,7 +72,7 @@ const handleCancel = () => {
|
|||||||
editable.value = false
|
editable.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = async (data: BaseModel) => {
|
const handleSave = async (data: WithResolved<BaseModel>) => {
|
||||||
await update(modelContent.value, data)
|
await update(modelContent.value, data)
|
||||||
editable.value = false
|
editable.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ import TabList from 'primevue/tablist'
|
|||||||
import TabPanel from 'primevue/tabpanel'
|
import TabPanel from 'primevue/tabpanel'
|
||||||
import TabPanels from 'primevue/tabpanels'
|
import TabPanels from 'primevue/tabpanels'
|
||||||
import Tabs from 'primevue/tabs'
|
import Tabs from 'primevue/tabs'
|
||||||
import { BaseModel } from 'types/typings'
|
import { BaseModel, WithResolved } from 'types/typings'
|
||||||
import { toRaw, watch } from 'vue'
|
import { toRaw, watch } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -73,7 +73,7 @@ const props = defineProps<Props>()
|
|||||||
const editable = defineModel<boolean>('editable')
|
const editable = defineModel<boolean>('editable')
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
submit: [formData: BaseModel]
|
submit: [formData: WithResolved<BaseModel>]
|
||||||
reset: []
|
reset: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,8 @@
|
|||||||
<div class="h-10"></div>
|
<div class="h-10"></div>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'flex h-10 items-center gap-4',
|
'absolute flex h-10 items-center gap-4',
|
||||||
'absolute left-1/2 -translate-x-1/2',
|
$xl('left-0 translate-x-0', 'left-1/2 -translate-x-1/2'),
|
||||||
$xl('left-0 translate-x-0'),
|
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -130,7 +130,13 @@
|
|||||||
<slot v-else name="desktop">
|
<slot v-else name="desktop">
|
||||||
<slot name="container">
|
<slot name="container">
|
||||||
<slot name="desktop:container">
|
<slot name="desktop:container">
|
||||||
<Menu ref="menu" :model="items" :popup="true" :base-z-index="1000">
|
<Menu
|
||||||
|
ref="menu"
|
||||||
|
:model="items"
|
||||||
|
:popup="true"
|
||||||
|
:base-z-index="1000"
|
||||||
|
:pt:root:style="{ maxHeight: '300px', overflowX: 'hidden' }"
|
||||||
|
>
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<slot name="item" :item="item">
|
<slot name="item" :item="item">
|
||||||
<slot name="desktop:container:item" :item="item">
|
<slot name="desktop:container:item" :item="item">
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ declare module 'hooks/store' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const configSetting = {
|
||||||
|
excludeScanTypes: 'ModelManager.Scan.excludeScanTypes',
|
||||||
|
}
|
||||||
|
|
||||||
function useAddConfigSettings(store: import('hooks/store').StoreProvider) {
|
function useAddConfigSettings(store: import('hooks/store').StoreProvider) {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -191,7 +195,7 @@ function useAddConfigSettings(store: import('hooks/store').StoreProvider) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.ui?.settings.addSetting({
|
app.ui?.settings.addSetting({
|
||||||
id: 'ModelManager.Scan.excludeScanTypes',
|
id: configSetting.excludeScanTypes,
|
||||||
category: [t('modelManager'), t('setting.scan'), 'ExcludeScanTypes'],
|
category: [t('modelManager'), t('setting.scan'), 'ExcludeScanTypes'],
|
||||||
name: t('setting.excludeScanTypes'),
|
name: t('setting.excludeScanTypes'),
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ export const useDialog = defineStore('dialog', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = (dialog: { key: string }) => {
|
const close = (dialog?: { key: string }) => {
|
||||||
|
if (!dialog) {
|
||||||
|
stack.value.pop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const item = stack.value.find((item) => item.key === dialog.key)
|
const item = stack.value.find((item) => item.key === dialog.key)
|
||||||
if (item?.keepAlive) {
|
if (item?.keepAlive) {
|
||||||
item.visible = false
|
item.visible = false
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { useMarkdown } from 'hooks/markdown'
|
|||||||
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 { cloneDeep } from 'lodash'
|
import { castArray, cloneDeep } from 'lodash'
|
||||||
import { app } from 'scripts/comfyAPI'
|
import { app } from 'scripts/comfyAPI'
|
||||||
import { BaseModel, Model, SelectEvent } from 'types/typings'
|
import { BaseModel, Model, SelectEvent, WithResolved } from 'types/typings'
|
||||||
import { bytesToSize, formatDate } from 'utils/common'
|
import { bytesToSize, formatDate, previewUrlToFile } from 'utils/common'
|
||||||
import { ModelGrid } from 'utils/legacy'
|
import { ModelGrid } from 'utils/legacy'
|
||||||
import { genModelKey, resolveModelTypeLoader } from 'utils/model'
|
import { genModelKey, resolveModelTypeLoader } from 'utils/model'
|
||||||
import {
|
import {
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
unref,
|
unref,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { configSetting } from './config'
|
||||||
|
|
||||||
type ModelFolder = Record<string, string[]>
|
type ModelFolder = Record<string, string[]>
|
||||||
|
|
||||||
@@ -56,23 +57,47 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
const refreshAllModels = async (force = false) => {
|
const refreshAllModels = async (force = false) => {
|
||||||
const forceRefresh = force ? refreshFolders() : Promise.resolve()
|
const forceRefresh = force ? refreshFolders() : Promise.resolve()
|
||||||
models.value = {}
|
models.value = {}
|
||||||
|
const excludeScanTypes = app.ui?.settings.getSettingValue<string>(
|
||||||
|
configSetting.excludeScanTypes,
|
||||||
|
)
|
||||||
|
const customBlackList =
|
||||||
|
excludeScanTypes
|
||||||
|
?.split(',')
|
||||||
|
.map((type) => type.trim())
|
||||||
|
.filter(Boolean) ?? []
|
||||||
return forceRefresh.then(() =>
|
return forceRefresh.then(() =>
|
||||||
Promise.allSettled(Object.keys(folders.value).map(refreshModels)),
|
Promise.allSettled(
|
||||||
|
Object.keys(folders.value)
|
||||||
|
.filter((folder) => !customBlackList.includes(folder))
|
||||||
|
.map(refreshModels),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateModel = async (model: BaseModel, data: BaseModel) => {
|
const updateModel = async (
|
||||||
const updateData = new Map()
|
model: BaseModel,
|
||||||
|
data: WithResolved<BaseModel>,
|
||||||
|
) => {
|
||||||
|
const updateData = new FormData()
|
||||||
let oldKey: string | null = null
|
let oldKey: string | null = null
|
||||||
|
let needUpdate = false
|
||||||
|
|
||||||
// Check current preview
|
// Check current preview
|
||||||
if (model.preview !== data.preview) {
|
if (model.preview !== data.preview) {
|
||||||
updateData.set('previewFile', data.preview)
|
const preview = data.preview
|
||||||
|
if (preview) {
|
||||||
|
const previewFile = await previewUrlToFile(data.preview as string)
|
||||||
|
updateData.set('previewFile', previewFile)
|
||||||
|
} else {
|
||||||
|
updateData.set('previewFile', 'undefined')
|
||||||
|
}
|
||||||
|
needUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check current description
|
// Check current description
|
||||||
if (model.description !== data.description) {
|
if (model.description !== data.description) {
|
||||||
updateData.set('description', data.description)
|
updateData.set('description', data.description)
|
||||||
|
needUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check current name and pathIndex
|
// Check current name and pathIndex
|
||||||
@@ -84,16 +109,17 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
updateData.set('type', data.type)
|
updateData.set('type', data.type)
|
||||||
updateData.set('pathIndex', data.pathIndex.toString())
|
updateData.set('pathIndex', data.pathIndex.toString())
|
||||||
updateData.set('fullname', data.fullname)
|
updateData.set('fullname', data.fullname)
|
||||||
|
needUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateData.size === 0) {
|
if (!needUpdate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.show()
|
loading.show()
|
||||||
await request(`/model/${model.type}/${model.pathIndex}/${model.fullname}`, {
|
await request(`/model/${model.type}/${model.pathIndex}/${model.fullname}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(Object.fromEntries(updateData.entries())),
|
body: updateData,
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const error_message = err.message ?? err.error
|
const error_message = err.message ?? err.error
|
||||||
@@ -203,15 +229,15 @@ export const useModelFormData = (getFormData: () => BaseModel) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubmitCallback = (data: BaseModel) => void
|
type SubmitCallback = (data: WithResolved<BaseModel>) => void
|
||||||
const submitCallback = ref<SubmitCallback[]>([])
|
const submitCallback = ref<SubmitCallback[]>([])
|
||||||
|
|
||||||
const registerSubmit = (callback: SubmitCallback) => {
|
const registerSubmit = (callback: SubmitCallback) => {
|
||||||
submitCallback.value.push(callback)
|
submitCallback.value.push(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = (): WithResolved<BaseModel> => {
|
||||||
const data = cloneDeep(toRaw(unref(formData)))
|
const data: any = cloneDeep(toRaw(unref(formData)))
|
||||||
for (const callback of submitCallback.value) {
|
for (const callback of submitCallback.value) {
|
||||||
callback(data)
|
callback(data)
|
||||||
}
|
}
|
||||||
@@ -381,9 +407,7 @@ export const useModelPreviewEditor = (formInstance: ModelFormInstance) => {
|
|||||||
* Default images
|
* Default images
|
||||||
*/
|
*/
|
||||||
const defaultContent = computed(() => {
|
const defaultContent = computed(() => {
|
||||||
return Array.isArray(model.value.preview)
|
return model.value.preview ? castArray(model.value.preview) : []
|
||||||
? model.value.preview
|
|
||||||
: [model.value.preview]
|
|
||||||
})
|
})
|
||||||
const defaultContentPage = ref(0)
|
const defaultContentPage = ref(0)
|
||||||
|
|
||||||
@@ -422,7 +446,7 @@ export const useModelPreviewEditor = (formInstance: ModelFormInstance) => {
|
|||||||
content = localContent.value
|
content = localContent.value
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
content = noPreviewContent.value
|
content = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +462,7 @@ export const useModelPreviewEditor = (formInstance: ModelFormInstance) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
registerSubmit((data) => {
|
registerSubmit((data) => {
|
||||||
data.preview = preview.value ?? noPreviewContent.value
|
data.preview = preview.value
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
4
src/types/typings.d.ts
vendored
4
src/types/typings.d.ts
vendored
@@ -26,6 +26,10 @@ export interface VersionModel extends BaseModel {
|
|||||||
hashes?: Record<string, string>
|
hashes?: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WithResolved<T> = Omit<T, 'preview'> & {
|
||||||
|
preview: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export type PassThrough<T = void> = T | object | undefined
|
export type PassThrough<T = void> = T | object | undefined
|
||||||
|
|
||||||
export interface SelectOptions {
|
export interface SelectOptions {
|
||||||
|
|||||||
@@ -26,3 +26,14 @@ export const bytesToSize = (
|
|||||||
export const formatDate = (date: number | string | Date) => {
|
export const formatDate = (date: number | string | Date) => {
|
||||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
|
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const previewUrlToFile = async (url: string) => {
|
||||||
|
return fetch(url)
|
||||||
|
.then((res) => res.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
const type = blob.type
|
||||||
|
const extension = type.split('/')[1]
|
||||||
|
const file = new File([blob], `preview.${extension}`, { type })
|
||||||
|
return file
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user