Skip to content
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

Merged
merged 40 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
57b138d
basic/empty model library sidebar tab
mcmonkey4eva Sep 15, 2024
9df8e33
make it actually list out models
mcmonkey4eva Sep 15, 2024
1a9e9b9
extremely primitive search impl
mcmonkey4eva Sep 15, 2024
2f7ff2e
list out available folders
mcmonkey4eva Sep 15, 2024
486db95
load list dynamically
mcmonkey4eva Sep 15, 2024
07cacec
nice lil loading icon
mcmonkey4eva Sep 15, 2024
6ffd685
that's not doing anything
mcmonkey4eva Sep 15, 2024
45fdb39
run autoformatter
mcmonkey4eva Sep 15, 2024
d8c641c
fix up some absolute vue shenanigans
mcmonkey4eva Sep 15, 2024
773b4cc
swap to pi-box
mcmonkey4eva Sep 15, 2024
d0731d8
is_fake_object
mcmonkey4eva Sep 15, 2024
c192818
i think apply the tailwind thingo
mcmonkey4eva Sep 15, 2024
4163af3
trim '.safetensors' from end of display title
mcmonkey4eva Sep 17, 2024
6dd9491
oop
mcmonkey4eva Sep 17, 2024
036bfc2
after load, retain title if no new title is given
mcmonkey4eva Sep 17, 2024
d9bae3b
is_load_requested to prevent duplication
mcmonkey4eva Sep 17, 2024
d2cd56c
dirty initial model metadata load & preview
mcmonkey4eva Sep 17, 2024
2d2ad1c
Merge branch 'main' into model-library-sidebar-tab
mcmonkey4eva Sep 17, 2024
9d9d776
update model store tests
mcmonkey4eva Sep 17, 2024
f3a4e61
initial image icon for model lib
mcmonkey4eva Sep 17, 2024
9b3f8ab
i hate this
mcmonkey4eva Sep 17, 2024
82ec4b2
better empty spacer
mcmonkey4eva Sep 17, 2024
e2a8878
Merge branch 'main' into model-library-sidebar-tab
mcmonkey4eva Sep 21, 2024
b2ff7b4
add api handler for '/models'
mcmonkey4eva Sep 21, 2024
600c087
load model folders list instead of hardcoding
mcmonkey4eva Sep 21, 2024
1ab37ef
add a 'no content' placeholder for empty folders
mcmonkey4eva Sep 21, 2024
2926b30
autoformat
mcmonkey4eva Sep 21, 2024
32c249b
autoload model metadata
mcmonkey4eva Sep 21, 2024
b8ff73a
error handling on metadata loading
mcmonkey4eva Sep 21, 2024
c72a0af
larger model icons
mcmonkey4eva Sep 21, 2024
2c42e52
click a model to spawn a node for it
mcmonkey4eva Sep 21, 2024
2b47da0
draggable model nodes
mcmonkey4eva Sep 21, 2024
ab8ed33
add a setting for whether to autoload or not
mcmonkey4eva Sep 21, 2024
120dc7f
autoformat will be the death of me
mcmonkey4eva Sep 21, 2024
305cc82
cleanup promise code
mcmonkey4eva Sep 21, 2024
a1a5e0d
make the model preview actually half-decent
mcmonkey4eva Sep 21, 2024
3a6d179
Merge branch 'main' into model-library-sidebar-tab
mcmonkey4eva Sep 22, 2024
2708688
revert bad unchecked change
mcmonkey4eva Sep 22, 2024
0375d42
Merge remote-tracking branch 'origin/dev1.3' into model-library-sideb…
mcmonkey4eva Sep 23, 2024
7ebe10f
put registration back
mcmonkey4eva Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useSettingStore } from './stores/settingStore'
import { useI18n } from 'vue-i18n'
import { useWorkspaceStore } from './stores/workspaceStateStore'
import NodeLibrarySidebarTab from './components/sidebar/tabs/NodeLibrarySidebarTab.vue'
import ModelLibrarySidebarTab from './components/sidebar/tabs/ModelLibrarySidebarTab.vue'
import GlobalDialog from './components/dialog/GlobalDialog.vue'
import GlobalToast from './components/toast/GlobalToast.vue'
import UnloadWindowConfirmDialog from './components/dialog/UnloadWindowConfirmDialog.vue'
Expand Down Expand Up @@ -100,6 +101,14 @@ const init = () => {
component: markRaw(NodeLibrarySidebarTab),
type: 'vue'
})
app.extensionManager.registerSidebarTab({
id: 'model-library',
icon: 'pi pi-folder-open',
Copy link
Member

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?

Copy link
Member Author

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

title: t('sideToolbar.modelLibrary'),
tooltip: t('sideToolbar.modelLibrary'),
component: markRaw(ModelLibrarySidebarTab),
type: 'vue'
})
}

const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
Expand Down
152 changes: 152 additions & 0 deletions src/components/sidebar/tabs/ModelLibrarySidebarTab.vue
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')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a isLoading getter on ComfyModel should make logic here more explicit.

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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 model.name.startsWith('\0')

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>
21 changes: 21 additions & 0 deletions src/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue
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>
3 changes: 3 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const messages = {
box: 'Box',
briefcase: 'Briefcase',
error: 'Error',
loading: 'Loading',
findIssues: 'Find Issues',
copyToClipboard: 'Copy to Clipboard',
openNewIssue: 'Open New Issue',
Expand All @@ -37,6 +38,7 @@ const messages = {
settings: 'Settings',
searchSettings: 'Search Settings',
searchNodes: 'Search Nodes',
searchModels: 'Search Models',
noResultsFound: 'No Results Found',
searchFailedMessage:
"We couldn't find any settings matching your search. Try adjusting your search terms.",
Expand All @@ -50,6 +52,7 @@ const messages = {
nodeLibraryTab: {
sortOrder: 'Sort Order'
},
modelLibrary: 'Model Library',
queueTab: {
showFlatList: 'Show Flat List',
backToAllTasks: 'Back to All Tasks',
Expand Down
2 changes: 1 addition & 1 deletion src/stores/modelStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class ComfyModelDef {

constructor(name: string, directory: string) {
this.name = name
this.title = name
this.title = name.replaceAll('\\', '/').split('/').pop()
mcmonkey4eva marked this conversation as resolved.
Show resolved Hide resolved
this.directory = directory
}

Expand Down
Loading