mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 22:09:55 +00:00
Optimize model library (#1739)
* Adapt experiment models api * Add model preview
This commit is contained in:
@@ -2,13 +2,10 @@
|
||||
<div ref="container" class="model-lib-node-container h-full w-full">
|
||||
<TreeExplorerTreeNode :node="node">
|
||||
<template #before-label>
|
||||
<span
|
||||
v-if="modelDef && modelDef.image"
|
||||
class="model-lib-model-icon-container"
|
||||
>
|
||||
<span v-if="modelPreviewUrl" class="model-lib-model-icon-container">
|
||||
<span
|
||||
class="model-lib-model-icon"
|
||||
:style="{ backgroundImage: `url(${modelDef.image})` }"
|
||||
:style="{ backgroundImage: `url(${modelPreviewUrl})` }"
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
@@ -44,6 +41,18 @@ const props = defineProps<{
|
||||
|
||||
const modelDef = computed(() => props.node.data)
|
||||
|
||||
const modelPreviewUrl = computed(() => {
|
||||
if (modelDef.value?.image) {
|
||||
return modelDef.value.image
|
||||
}
|
||||
const folder = modelDef.value.directory
|
||||
const path_index = modelDef.value.path_index
|
||||
const extension = modelDef.value.file_name.split('.').pop()
|
||||
const filename = modelDef.value.file_name.replace(`.${extension}`, '.webp')
|
||||
const encodedFilename = encodeURIComponent(filename).replace(/%2F/g, '/')
|
||||
return `/api/experiment/models/preview/${folder}/${path_index}/${encodedFilename}`
|
||||
})
|
||||
|
||||
const previewRef = ref<InstanceType<typeof ModelPreview> | null>(null)
|
||||
const modelPreviewStyle = ref<CSSProperties>({
|
||||
position: 'absolute',
|
||||
@@ -129,9 +138,10 @@ onUnmounted(() => {
|
||||
background-position: center;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: -2.5rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
left: -2.2rem;
|
||||
top: -0.1rem;
|
||||
height: 1.7rem;
|
||||
width: 1.7rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -481,8 +481,8 @@ export class ComfyApi extends EventTarget {
|
||||
* Gets a list of model folder keys (eg ['checkpoints', 'loras', ...])
|
||||
* @returns The list of model folder keys
|
||||
*/
|
||||
async getModelFolders(): Promise<string[]> {
|
||||
const res = await this.fetchApi(`/models`)
|
||||
async getModelFolders(): Promise<{ name: string; folders: string[] }[]> {
|
||||
const res = await this.fetchApi(`/experiment/models`)
|
||||
if (res.status === 404) {
|
||||
return []
|
||||
}
|
||||
@@ -497,8 +497,10 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string} folder The folder to list models from, such as 'checkpoints'
|
||||
* @returns The list of model filenames within the specified folder
|
||||
*/
|
||||
async getModels(folder: string): Promise<string[]> {
|
||||
const res = await this.fetchApi(`/models/${folder}`)
|
||||
async getModels(
|
||||
folder: string
|
||||
): Promise<{ name: string; pathIndex: number }[]> {
|
||||
const res = await this.fetchApi(`/experiment/models/${folder}`)
|
||||
if (res.status === 404) {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ function _findInMetadata(metadata: any, ...keys: string[]): string | null {
|
||||
|
||||
/** Defines and holds metadata for a model */
|
||||
export class ComfyModelDef {
|
||||
/** Path to the model */
|
||||
readonly path_index: number
|
||||
/** Proper filename of the model */
|
||||
readonly file_name: string
|
||||
/** Normalized filename of the model, with all backslashes replaced with forward slashes */
|
||||
@@ -54,7 +56,8 @@ export class ComfyModelDef {
|
||||
/** A string full of auto-computed lowercase-only searchable text for this model */
|
||||
searchable: string = ''
|
||||
|
||||
constructor(name: string, directory: string) {
|
||||
constructor(name: string, directory: string, pathIndex: number) {
|
||||
this.path_index = pathIndex
|
||||
this.file_name = name
|
||||
this.normalized_file_name = name.replaceAll('\\', '/')
|
||||
this.simplified_file_name = this.normalized_file_name.split('/').pop() ?? ''
|
||||
@@ -165,7 +168,11 @@ export class ModelFolder {
|
||||
this.state = ResourceState.Loading
|
||||
const models = await api.getModels(this.directory)
|
||||
for (const model of models) {
|
||||
this.models[model] = new ComfyModelDef(model, this.directory)
|
||||
this.models[`${model.pathIndex}/${model.name}`] = new ComfyModelDef(
|
||||
model.name,
|
||||
this.directory,
|
||||
model.pathIndex
|
||||
)
|
||||
}
|
||||
this.state = ResourceState.Loaded
|
||||
return this
|
||||
@@ -189,7 +196,8 @@ export const useModelStore = defineStore('models', () => {
|
||||
* Loads the model folders from the server
|
||||
*/
|
||||
async function loadModelFolders() {
|
||||
modelFolderNames.value = await api.getModelFolders()
|
||||
const resData = await api.getModelFolders()
|
||||
modelFolderNames.value = resData.map((folder) => folder.name)
|
||||
modelFolderByName.value = {}
|
||||
for (const folderName of modelFolderNames.value) {
|
||||
modelFolderByName.value[folderName] = new ModelFolder(folderName)
|
||||
|
||||
@@ -13,11 +13,14 @@ jest.mock('@/scripts/api', () => ({
|
||||
|
||||
function enableMocks() {
|
||||
;(api.getModels as jest.Mock).mockResolvedValue([
|
||||
'sdxl.safetensors',
|
||||
'sdv15.safetensors',
|
||||
'noinfo.safetensors'
|
||||
{ name: 'sdxl.safetensors', pathIndex: 0 },
|
||||
{ name: 'sdv15.safetensors', pathIndex: 0 },
|
||||
{ name: 'noinfo.safetensors', pathIndex: 0 }
|
||||
])
|
||||
;(api.getModelFolders as jest.Mock).mockResolvedValue([
|
||||
{ name: 'checkpoints', folders: ['/path/to/checkpoints'] },
|
||||
{ name: 'vae', folders: ['/path/to/vae'] }
|
||||
])
|
||||
;(api.getModelFolders as jest.Mock).mockResolvedValue(['checkpoints', 'vae'])
|
||||
;(api.viewMetadata as jest.Mock).mockImplementation((_, model) => {
|
||||
if (model === 'noinfo.safetensors') {
|
||||
return Promise.resolve({})
|
||||
@@ -59,7 +62,7 @@ describe('useModelStore', () => {
|
||||
const folderStore = await store.getLoadedModelFolder('checkpoints')
|
||||
expect(folderStore).not.toBeNull()
|
||||
if (!folderStore) return
|
||||
const model = folderStore.models['sdxl.safetensors']
|
||||
const model = folderStore.models['0/sdxl.safetensors']
|
||||
await model.load()
|
||||
expect(model.title).toBe('Title of sdxl.safetensors')
|
||||
expect(model.architecture_id).toBe('stable-diffusion-xl-base-v1')
|
||||
@@ -77,7 +80,7 @@ describe('useModelStore', () => {
|
||||
const folderStore = await store.getLoadedModelFolder('checkpoints')
|
||||
expect(folderStore).not.toBeNull()
|
||||
if (!folderStore) return
|
||||
const model = folderStore.models['noinfo.safetensors']
|
||||
const model = folderStore.models['0/noinfo.safetensors']
|
||||
await model.load()
|
||||
expect(model.file_name).toBe('noinfo.safetensors')
|
||||
expect(model.title).toBe('noinfo')
|
||||
|
||||
Reference in New Issue
Block a user