Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00d23ff74f | ||
|
|
dc46f498be | ||
|
|
6d67b00b17 | ||
|
|
cda24405b5 | ||
|
|
6fa90be8c4 |
17
__init__.py
17
__init__.py
@@ -89,6 +89,7 @@ async def delete_model_download_task(request):
|
|||||||
return web.json_response({"success": False, "error": error_msg})
|
return web.json_response({"success": False, "error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
|
# @deprecated
|
||||||
@routes.get("/model-manager/base-folders")
|
@routes.get("/model-manager/base-folders")
|
||||||
async def get_model_paths(request):
|
async def get_model_paths(request):
|
||||||
"""
|
"""
|
||||||
@@ -124,12 +125,12 @@ async def create_model(request):
|
|||||||
|
|
||||||
|
|
||||||
@routes.get("/model-manager/models")
|
@routes.get("/model-manager/models")
|
||||||
async def read_models(request):
|
async def list_model_types(request):
|
||||||
"""
|
"""
|
||||||
Scan all models and read their information.
|
Scan all models and read their information.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = services.scan_models(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)}"
|
||||||
@@ -137,6 +138,18 @@ async def read_models(request):
|
|||||||
return web.json_response({"success": False, "error": error_msg})
|
return web.json_response({"success": False, "error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/model-manager/models/{folder}")
|
||||||
|
async def read_models(request):
|
||||||
|
try:
|
||||||
|
folder = request.match_info.get("folder", None)
|
||||||
|
results = services.scan_models(folder, request)
|
||||||
|
return web.json_response({"success": True, "data": results})
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Read models failed: {str(e)}"
|
||||||
|
utils.print_error(error_msg)
|
||||||
|
return web.json_response({"success": False, "error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/model-manager/model/{type}/{index}/{filename:.*}")
|
@routes.get("/model-manager/model/{type}/{index}/{filename:.*}")
|
||||||
async def read_model_info(request):
|
async def read_model_info(request):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,47 +8,46 @@ from . import download
|
|||||||
from . import searcher
|
from . import searcher
|
||||||
|
|
||||||
|
|
||||||
def scan_models(request):
|
def scan_models(folder: str, request):
|
||||||
result = []
|
result = []
|
||||||
model_base_paths = utils.resolve_model_base_paths()
|
|
||||||
for model_type in model_base_paths:
|
|
||||||
|
|
||||||
folders, extensions = folder_paths.folder_names_and_paths[model_type]
|
folders, extensions = folder_paths.folder_names_and_paths[folder]
|
||||||
for path_index, base_path in enumerate(folders):
|
for path_index, base_path in enumerate(folders):
|
||||||
files = utils.recursive_search_files(base_path, request)
|
files = utils.recursive_search_files(base_path, request)
|
||||||
|
|
||||||
models = folder_paths.filter_files_extensions(files, folder_paths.supported_pt_extensions)
|
models = folder_paths.filter_files_extensions(files, folder_paths.supported_pt_extensions)
|
||||||
|
|
||||||
for fullname in models:
|
for fullname in models:
|
||||||
fullname = utils.normalize_path(fullname)
|
fullname = utils.normalize_path(fullname)
|
||||||
basename = os.path.splitext(fullname)[0]
|
basename = os.path.splitext(fullname)[0]
|
||||||
extension = os.path.splitext(fullname)[1]
|
extension = os.path.splitext(fullname)[1]
|
||||||
|
|
||||||
abs_path = utils.join_path(base_path, fullname)
|
abs_path = utils.join_path(base_path, fullname)
|
||||||
file_stats = os.stat(abs_path)
|
file_stats = os.stat(abs_path)
|
||||||
|
|
||||||
# Resolve preview
|
# Resolve preview
|
||||||
image_name = utils.get_model_preview_name(abs_path)
|
image_name = utils.get_model_preview_name(abs_path)
|
||||||
abs_image_path = utils.join_path(base_path, image_name)
|
image_name = utils.join_path(os.path.dirname(fullname), image_name)
|
||||||
if os.path.isfile(abs_image_path):
|
abs_image_path = utils.join_path(base_path, image_name)
|
||||||
image_state = os.stat(abs_image_path)
|
if os.path.isfile(abs_image_path):
|
||||||
image_timestamp = round(image_state.st_mtime_ns / 1000000)
|
image_state = os.stat(abs_image_path)
|
||||||
image_name = f"{image_name}?ts={image_timestamp}"
|
image_timestamp = round(image_state.st_mtime_ns / 1000000)
|
||||||
model_preview = f"/model-manager/preview/{model_type}/{path_index}/{image_name}"
|
image_name = f"{image_name}?ts={image_timestamp}"
|
||||||
|
model_preview = f"/model-manager/preview/{folder}/{path_index}/{image_name}"
|
||||||
|
|
||||||
model_info = {
|
model_info = {
|
||||||
"fullname": fullname,
|
"fullname": fullname,
|
||||||
"basename": basename,
|
"basename": basename,
|
||||||
"extension": extension,
|
"extension": extension,
|
||||||
"type": model_type,
|
"type": folder,
|
||||||
"pathIndex": path_index,
|
"pathIndex": path_index,
|
||||||
"sizeBytes": file_stats.st_size,
|
"sizeBytes": file_stats.st_size,
|
||||||
"preview": model_preview,
|
"preview": model_preview,
|
||||||
"createdAt": round(file_stats.st_ctime_ns / 1000000),
|
"createdAt": round(file_stats.st_ctime_ns / 1000000),
|
||||||
"updatedAt": round(file_stats.st_mtime_ns / 1000000),
|
"updatedAt": round(file_stats.st_mtime_ns / 1000000),
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(model_info)
|
result.append(model_info)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -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.1.2"
|
version = "2.1.4"
|
||||||
license = "LICENSE"
|
license = "LICENSE"
|
||||||
dependencies = ["markdownify"]
|
dependencies = ["markdownify"]
|
||||||
|
|
||||||
|
|||||||
11
src/App.vue
11
src/App.vue
@@ -15,16 +15,18 @@ import { useStoreProvider } from 'hooks/store'
|
|||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
import GlobalConfirm from 'primevue/confirmdialog'
|
import GlobalConfirm from 'primevue/confirmdialog'
|
||||||
import { $el, app, ComfyButton } from 'scripts/comfyAPI'
|
import { $el, app, ComfyButton } from 'scripts/comfyAPI'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { dialog, models, config, download } = useStoreProvider()
|
const { dialog, models, config, download } = useStoreProvider()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
|
const firstOpenManager = ref(true)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const refreshModelsAndConfig = async () => {
|
const refreshModelsAndConfig = async () => {
|
||||||
await Promise.all([models.refresh(), config.refresh()])
|
await Promise.all([models.refresh(true)])
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: 'Refreshed Models',
|
summary: 'Refreshed Models',
|
||||||
@@ -50,6 +52,11 @@ onMounted(() => {
|
|||||||
const openManagerDialog = () => {
|
const openManagerDialog = () => {
|
||||||
const { cardWidth, gutter, aspect } = config
|
const { cardWidth, gutter, aspect } = config
|
||||||
|
|
||||||
|
if (firstOpenManager.value) {
|
||||||
|
models.refresh(true)
|
||||||
|
firstOpenManager.value = false
|
||||||
|
}
|
||||||
|
|
||||||
dialog.open({
|
dialog.open({
|
||||||
key: 'model-manager',
|
key: 'model-manager',
|
||||||
title: t('modelManager'),
|
title: t('modelManager'),
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ import { genModelKey } from 'utils/model'
|
|||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const { isMobile, cardWidth, gutter, aspect, modelFolders } = useConfig()
|
const { isMobile, cardWidth, gutter, aspect } = useConfig()
|
||||||
|
|
||||||
const { data } = useModels()
|
const { data, folders } = useModels()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const responseScroll = ref()
|
const responseScroll = ref()
|
||||||
@@ -99,7 +99,7 @@ const searchContent = ref<string>()
|
|||||||
|
|
||||||
const currentType = ref('all')
|
const currentType = ref('all')
|
||||||
const typeOptions = computed(() => {
|
const typeOptions = computed(() => {
|
||||||
return ['all', ...Object.keys(modelFolders.value)].map((type) => {
|
return ['all', ...Object.keys(folders.value)].map((type) => {
|
||||||
return {
|
return {
|
||||||
label: type,
|
label: type,
|
||||||
value: type,
|
value: type,
|
||||||
@@ -143,7 +143,9 @@ const colSpan = ref(1)
|
|||||||
const colSpanWidth = ref(cardWidth)
|
const colSpanWidth = ref(cardWidth)
|
||||||
|
|
||||||
const list = computed(() => {
|
const list = computed(() => {
|
||||||
const filterList = data.value.filter((model) => {
|
const mergedList = Object.values(data.value).flat()
|
||||||
|
|
||||||
|
const filterList = mergedList.filter((model) => {
|
||||||
const showAllModel = currentType.value === 'all'
|
const showAllModel = currentType.value === 'all'
|
||||||
|
|
||||||
const matchType = showAllModel || model.type === currentType.value
|
const matchType = showAllModel || model.type === currentType.value
|
||||||
|
|||||||
@@ -49,15 +49,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ResponseInput from 'components/ResponseInput.vue'
|
import ResponseInput from 'components/ResponseInput.vue'
|
||||||
import ResponseSelect from 'components/ResponseSelect.vue'
|
import ResponseSelect from 'components/ResponseSelect.vue'
|
||||||
import { useConfig } from 'hooks/config'
|
|
||||||
import { useModelBaseInfo } from 'hooks/model'
|
import { useModelBaseInfo } from 'hooks/model'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const editable = defineModel<boolean>('editable')
|
const editable = defineModel<boolean>('editable')
|
||||||
|
|
||||||
const { modelFolders } = useConfig()
|
const { baseInfo, pathIndex, basename, extension, type, modelFolders } =
|
||||||
|
useModelBaseInfo()
|
||||||
const { baseInfo, pathIndex, basename, extension, type } = useModelBaseInfo()
|
|
||||||
|
|
||||||
const typeOptions = computed(() => {
|
const typeOptions = computed(() => {
|
||||||
return Object.keys(modelFolders.value).map((curr) => {
|
return Object.keys(modelFolders.value).map((curr) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { request, useRequest } from 'hooks/request'
|
import { request } from 'hooks/request'
|
||||||
import { defineStore } from 'hooks/store'
|
import { defineStore } from 'hooks/store'
|
||||||
import { $el, app, ComfyDialog } from 'scripts/comfyAPI'
|
import { $el, app, ComfyDialog } from 'scripts/comfyAPI'
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
@@ -8,10 +8,6 @@ export const useConfig = defineStore('config', (store) => {
|
|||||||
const mobileDeviceBreakPoint = 759
|
const mobileDeviceBreakPoint = 759
|
||||||
const isMobile = ref(window.innerWidth < mobileDeviceBreakPoint)
|
const isMobile = ref(window.innerWidth < mobileDeviceBreakPoint)
|
||||||
|
|
||||||
type ModelFolder = Record<string, string[]>
|
|
||||||
const { data: modelFolders, refresh: refreshModelFolders } =
|
|
||||||
useRequest<ModelFolder>('/base-folders')
|
|
||||||
|
|
||||||
const checkDeviceType = () => {
|
const checkDeviceType = () => {
|
||||||
isMobile.value = window.innerWidth < mobileDeviceBreakPoint
|
isMobile.value = window.innerWidth < mobileDeviceBreakPoint
|
||||||
}
|
}
|
||||||
@@ -24,17 +20,11 @@ export const useConfig = defineStore('config', (store) => {
|
|||||||
window.removeEventListener('resize', checkDeviceType)
|
window.removeEventListener('resize', checkDeviceType)
|
||||||
})
|
})
|
||||||
|
|
||||||
const refresh = async () => {
|
|
||||||
return Promise.all([refreshModelFolders()])
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
isMobile,
|
isMobile,
|
||||||
gutter: 16,
|
gutter: 16,
|
||||||
cardWidth: 240,
|
cardWidth: 240,
|
||||||
aspect: 7 / 9,
|
aspect: 7 / 9,
|
||||||
modelFolders,
|
|
||||||
refresh,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useAddConfigSettings(store)
|
useAddConfigSettings(store)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useConfig } from 'hooks/config'
|
|
||||||
import { useLoading } from 'hooks/loading'
|
import { useLoading } from 'hooks/loading'
|
||||||
import { useMarkdown } from 'hooks/markdown'
|
import { useMarkdown } from 'hooks/markdown'
|
||||||
import { request, useRequest } 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 { cloneDeep } from 'lodash'
|
||||||
@@ -22,12 +21,45 @@ import {
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
type ModelFolder = Record<string, string[]>
|
||||||
|
|
||||||
|
const modelFolderProvideKey = Symbol('modelFolder')
|
||||||
|
|
||||||
export const useModels = defineStore('models', (store) => {
|
export const useModels = defineStore('models', (store) => {
|
||||||
const { data, refresh } = useRequest<Model[]>('/models', { defaultValue: [] })
|
|
||||||
const { toast, confirm } = useToast()
|
const { toast, confirm } = useToast()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const loading = useLoading()
|
const loading = useLoading()
|
||||||
|
|
||||||
|
const folders = ref<ModelFolder>({})
|
||||||
|
const refreshFolders = async () => {
|
||||||
|
return request('/models').then((resData) => {
|
||||||
|
folders.value = resData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(modelFolderProvideKey, folders)
|
||||||
|
|
||||||
|
const models = ref<Record<string, Model[]>>({})
|
||||||
|
|
||||||
|
const refreshModels = async (folder: string) => {
|
||||||
|
loading.show()
|
||||||
|
return request(`/models/${folder}`)
|
||||||
|
.then((resData) => {
|
||||||
|
models.value[folder] = resData
|
||||||
|
return resData
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.hide()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshAllModels = async (force = false) => {
|
||||||
|
const forceRefresh = force ? refreshFolders() : Promise.resolve()
|
||||||
|
return forceRefresh.then(() =>
|
||||||
|
Promise.allSettled(Object.keys(folders.value).map(refreshModels)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const updateModel = async (model: BaseModel, data: BaseModel) => {
|
const updateModel = async (model: BaseModel, data: BaseModel) => {
|
||||||
const updateData = new Map()
|
const updateData = new Map()
|
||||||
let oldKey: string | null = null
|
let oldKey: string | null = null
|
||||||
@@ -80,7 +112,7 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
store.dialog.close({ key: oldKey })
|
store.dialog.close({ key: oldKey })
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh()
|
refreshModels(data.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteModel = async (model: BaseModel) => {
|
const deleteModel = async (model: BaseModel) => {
|
||||||
@@ -112,7 +144,7 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
life: 2000,
|
life: 2000,
|
||||||
})
|
})
|
||||||
store.dialog.close({ key: dialogKey })
|
store.dialog.close({ key: dialogKey })
|
||||||
return refresh()
|
return refreshModels(model.type)
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve(void 0)
|
resolve(void 0)
|
||||||
@@ -136,7 +168,13 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data, refresh, remove: deleteModel, update: updateModel }
|
return {
|
||||||
|
folders: folders,
|
||||||
|
data: models,
|
||||||
|
refresh: refreshAllModels,
|
||||||
|
remove: deleteModel,
|
||||||
|
update: updateModel,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
declare module 'hooks/store' {
|
declare module 'hooks/store' {
|
||||||
@@ -204,7 +242,10 @@ const baseInfoKey = Symbol('baseInfo') as InjectionKey<
|
|||||||
export const useModelBaseInfoEditor = (formInstance: ModelFormInstance) => {
|
export const useModelBaseInfoEditor = (formInstance: ModelFormInstance) => {
|
||||||
const { formData: model, modelData } = formInstance
|
const { formData: model, modelData } = formInstance
|
||||||
|
|
||||||
const { modelFolders } = useConfig()
|
const provideModelFolders = inject<any>(modelFolderProvideKey)
|
||||||
|
const modelFolders = computed<ModelFolder>(() => {
|
||||||
|
return provideModelFolders?.value ?? {}
|
||||||
|
})
|
||||||
|
|
||||||
const type = computed({
|
const type = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
@@ -304,6 +345,7 @@ export const useModelBaseInfoEditor = (formInstance: ModelFormInstance) => {
|
|||||||
basename,
|
basename,
|
||||||
extension,
|
extension,
|
||||||
pathIndex,
|
pathIndex,
|
||||||
|
modelFolders,
|
||||||
}
|
}
|
||||||
|
|
||||||
provide(baseInfoKey, result)
|
provide(baseInfoKey, result)
|
||||||
|
|||||||
Reference in New Issue
Block a user