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