-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Draft] Model library sidebar tab #837
Changes from 9 commits
57b138d
9df8e33
1a9e9b9
2f7ff2e
486db95
07cacec
6ffd685
45fdb39
d8c641c
773b4cc
d0731d8
c192818
4163af3
6dd9491
036bfc2
d9bae3b
d2cd56c
2d2ad1c
9d9d776
f3a4e61
9b3f8ab
82ec4b2
e2a8878
b2ff7b4
600c087
1ab37ef
2926b30
32c249b
b8ff73a
c72a0af
2c42e52
2b47da0
ab8ed33
120dc7f
305cc82
a1a5e0d
3a6d179
2708688
0375d42
7ebe10f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<template> | ||
<SidebarTabTemplate :title="$t('sideToolbar.modelLibrary')"> | ||
<template #tool-buttons> </template> | ||
<template #body> | ||
<div class="flex flex-col h-full"> | ||
<div class="flex-shrink-0"> | ||
<SearchBox | ||
class="model-lib-search-box mx-4 mt-4" | ||
v-model:modelValue="searchQuery" | ||
@search="handleSearch" | ||
:placeholder="$t('searchModels') + '...'" | ||
/> | ||
</div> | ||
<div class="flex-grow overflow-y-auto"> | ||
<TreeExplorer | ||
class="model-lib-tree-explorer mt-1" | ||
:roots="renderedRoot.children" | ||
v-model:expandedKeys="expandedKeys" | ||
@nodeClick="handleNodeClick" | ||
> | ||
<template #node="{ node }"> | ||
<ModelTreeLeaf :node="node" /> | ||
</template> | ||
</TreeExplorer> | ||
</div> | ||
</div> | ||
</template> | ||
</SidebarTabTemplate> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import SearchBox from '@/components/common/SearchBox.vue' | ||
import { useI18n } from 'vue-i18n' | ||
import TreeExplorer from '@/components/common/TreeExplorer.vue' | ||
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue' | ||
import ModelTreeLeaf from '@/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue' | ||
import { ComfyModelDef, useModelStore } from '@/stores/modelStore' | ||
import { useTreeExpansion } from '@/hooks/treeHooks' | ||
import type { | ||
RenderedTreeExplorerNode, | ||
TreeExplorerNode | ||
} from '@/types/treeExplorerTypes' | ||
import { computed, ref, type ComputedRef, watch, toRef } from 'vue' | ||
import type { TreeNode } from 'primevue/treenode' | ||
import { buildTree } from '@/utils/treeUtil' | ||
|
||
const { t } = useI18n() | ||
const modelStore = useModelStore() | ||
const searchQuery = ref<string>('') | ||
const expandedKeys = ref<Record<string, boolean>>({}) | ||
const { toggleNodeOnEvent } = useTreeExpansion(expandedKeys) | ||
|
||
const rootFolders = ['checkpoints', 'loras', 'vae', 'controlnet'] | ||
|
||
const root: ComputedRef<TreeNode> = computed(() => { | ||
let modelList: ComfyModelDef[] = [] | ||
for (let folder of rootFolders) { | ||
const models = modelStore.modelStoreMap[folder] | ||
if (models) { | ||
modelList.push(...Object.values(models.models)) | ||
} else { | ||
modelList.push(new ComfyModelDef('\0Loading', folder)) | ||
} | ||
} | ||
if (searchQuery.value) { | ||
const search = searchQuery.value.toLocaleLowerCase() | ||
modelList = modelList.filter((model: ComfyModelDef) => { | ||
return model.name.toLocaleLowerCase().includes(search) | ||
}) | ||
} | ||
const tree: TreeNode = buildTree(modelList, (model: ComfyModelDef) => { | ||
return [model.directory, ...model.name.replaceAll('\\', '/').split('/')] | ||
mcmonkey4eva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
return tree | ||
}) | ||
|
||
const renderedRoot = computed<TreeExplorerNode<ComfyModelDef>>(() => { | ||
const fillNodeInfo = (node: TreeNode): TreeExplorerNode<ComfyModelDef> => { | ||
const children = node.children?.map(fillNodeInfo) | ||
const model: ComfyModelDef | null = | ||
node.leaf && node.data ? node.data : null | ||
if (model && model.name.startsWith('\0')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ngl every version of this code will be a hack other than directly adding the loading entry to the tree instead of passing a fake def, but that adds significant code complexity for the sake of dodging the hack. But yeah I'll add a bool on the modeldef to at least mark it a bit more properly than this nullchar indicator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessary adding a new field, you just need to add a getter that returns the expression |
||
return { | ||
key: node.key, | ||
label: t('loading') + '...', | ||
leaf: true, | ||
data: null, | ||
getIcon: (node: TreeExplorerNode<ComfyModelDef>) => { | ||
return 'pi pi-spin pi-spinner' | ||
}, | ||
children: [] | ||
} | ||
} | ||
|
||
return { | ||
key: node.key, | ||
label: model ? model.title : node.label, | ||
leaf: node.leaf, | ||
data: node.data, | ||
getIcon: (node: TreeExplorerNode<ComfyModelDef>) => { | ||
if (node.leaf) { | ||
return 'pi pi-file' | ||
} | ||
}, | ||
children, | ||
draggable: node.leaf | ||
} | ||
} | ||
return fillNodeInfo(root.value) | ||
}) | ||
|
||
const handleSearch = (query: string) => { | ||
// TODO | ||
} | ||
|
||
const handleNodeClick = ( | ||
node: RenderedTreeExplorerNode<ComfyModelDef>, | ||
e: MouseEvent | ||
) => { | ||
if (node.leaf) { | ||
// TODO | ||
} else { | ||
toggleNodeOnEvent(e, node) | ||
} | ||
} | ||
|
||
watch( | ||
toRef(expandedKeys, 'value'), | ||
(newExpandedKeys) => { | ||
Object.entries(newExpandedKeys).forEach(([key, isExpanded]) => { | ||
if (isExpanded) { | ||
const folderPath = key.split('/').slice(1).join('/') | ||
if (folderPath && !folderPath.includes('/')) { | ||
// Trigger (async) load of model data for this folder | ||
modelStore.getModelsInFolderCached(folderPath) | ||
} | ||
} | ||
}) | ||
}, | ||
{ deep: true } | ||
) | ||
</script> | ||
|
||
<style> | ||
/* TODO */ | ||
</style> | ||
|
||
<style scoped> | ||
:deep(.comfy-vue-side-bar-body) { | ||
background: var(--p-tree-background); | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<template> | ||
<div ref="container" class="model-lib-node-container"> | ||
<TreeExplorerTreeNode :node="node"> </TreeExplorerTreeNode> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue' | ||
import { ComfyModelDef } from '@/stores/modelStore' | ||
import { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes' | ||
|
||
const props = defineProps<{ | ||
node: RenderedTreeExplorerNode<ComfyModelDef> | ||
}>() | ||
</script> | ||
|
||
<style scoped> | ||
.model-lib-node-container { | ||
huchenlei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@apply h-full w-full; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably pick another icon. Maybe
pi-box
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah wasn't sure what icon would work here. Swapped to box