diff --git a/src/components/tree-browsers/project-tree-browser/index.tsx b/src/components/editors/EntityEditor/EntityTree/index.tsx
similarity index 50%
rename from src/components/tree-browsers/project-tree-browser/index.tsx
rename to src/components/editors/EntityEditor/EntityTree/index.tsx
index 94d1bb84..66030894 100644
--- a/src/components/tree-browsers/project-tree-browser/index.tsx
+++ b/src/components/editors/EntityEditor/EntityTree/index.tsx
@@ -1,15 +1,20 @@
'use client'
import { cn } from '@/lib/utils'
-import { isClapEntity } from '../utils/isSomething'
-import { useProjectLibrary } from '../stores/useProjectLibrary'
-import { LibraryNodeItem, LibraryNodeType } from '../types'
+import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
+import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
import { Tree } from '@/components/core/tree'
-export function ProjectTreeBrowser() {
- const libraryTreeRoot = useProjectLibrary((s) => s.libraryTreeRoot)
- const selectTreeNode = useProjectLibrary((s) => s.selectTreeNode)
- const selectedTreeNodeId = useProjectLibrary((s) => s.selectedTreeNodeId)
+import { useEntityTree } from './useEntityTree'
+
+export function EntityTree({
+ className = '',
+}: {
+ className?: string
+} = {}) {
+ const libraryTreeRoot = useEntityTree((s) => s.libraryTreeRoot)
+ const selectTreeNode = useEntityTree((s) => s.selectTreeNode)
+ const selectedTreeNodeId = useEntityTree((s) => s.selectedTreeNodeId)
/**
* handle click on tree node
@@ -23,7 +28,7 @@ export function ProjectTreeBrowser() {
const handleOnChange = async (
id: string | null,
nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
+ nodeItem?: TreeNodeItem
) => {
console.log(`calling selectTreeNodeById(id)`)
selectTreeNode(id, nodeType, nodeItem)
@@ -44,17 +49,15 @@ export function ProjectTreeBrowser() {
}
return (
-
-
- value={selectedTreeNodeId}
- onChange={handleOnChange}
- className="not-prose h-full w-full px-2 pt-8"
- label="Project Library"
- >
- {libraryTreeRoot.map((node) => (
-
- ))}
-
-
+
+ value={selectedTreeNodeId}
+ onChange={handleOnChange}
+ className={cn(`not-prose h-full w-full px-2 pt-2`, className)}
+ label="Entities"
+ >
+ {libraryTreeRoot.map((node) => (
+
+ ))}
+
)
}
diff --git a/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts b/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts
new file mode 100644
index 00000000..d8f3fe8c
--- /dev/null
+++ b/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts
@@ -0,0 +1,207 @@
+'use client'
+
+import { create } from 'zustand'
+import { ClapEntity, UUID } from '@aitube/clap'
+import {
+ LibraryTreeNode,
+ TreeNodeItem,
+ LibraryNodeType,
+} from '@/components/tree-browsers/types'
+import { icons } from '@/components/icons'
+import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
+import {
+ collectionClassName,
+ libraryClassName,
+} from '@/components/tree-browsers/style/treeNodeStyles'
+
+export const useEntityTree = create<{
+ // project entities stored in the .clap
+ projectLibraryTreeNodeId: string
+
+ // in the future, we are going to put
+ //
+
+ // entities stored on the public database (Hugging Face datasets, tagged)
+ communityLibraryTreeNodeId: string
+
+ libraryTreeRoot: LibraryTreeNode[]
+ init: () => void
+
+ /**
+ * Load entity collections (characters, locations..) from the clap project into the tree
+ *
+ * @param collections
+ * @returns
+ */
+ // setProjectLibrary: (collections: ProjectEntityCollection[]) => void
+
+ /**
+ * Load entity collections (characters, locations..) from the Clapper community into the tree
+ *
+ * @param collections
+ * @returns
+ */
+ // setCommunityLibrary: (collections: CommunityEntityCollection[]) => void
+
+ // we support those all selection modes for convenience - please keep them!
+ selectedNodeItem?: TreeNodeItem
+ selectedNodeType?: LibraryNodeType
+ selectTreeNode: (
+ treeNodeId?: string | null,
+ nodeType?: LibraryNodeType,
+ nodeItem?: TreeNodeItem
+ ) => void
+ selectedTreeNodeId: string | null
+}>((set, get) => ({
+ // project entities stored in the .clap
+ projectLibraryTreeNodeId: '',
+
+ // in the future, we are going to put
+ //
+
+ // entities stored on the public database (Hugging Face datasets, tagged)
+ communityLibraryTreeNodeId: '',
+ libraryTreeRoot: [],
+ init: () => {
+ const projectLibrary: LibraryTreeNode = {
+ id: UUID(),
+ nodeType: 'TREE_ROOT_PROJECT',
+ label: 'Project entities',
+ icon: icons.project,
+ className: libraryClassName,
+ isExpanded: true,
+ children: [
+ {
+ id: UUID(),
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
+ label: 'Empty',
+ icon: icons.project,
+ className: collectionClassName,
+ },
+ ],
+ }
+
+ const communityLibrary: LibraryTreeNode = {
+ id: UUID(),
+ nodeType: 'TREE_ROOT_COMMUNITY',
+ label: 'Community entities',
+ icon: icons.community,
+ className: libraryClassName,
+ children: [
+ {
+ id: UUID(),
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
+ label: 'Empty',
+ icon: icons.community,
+ className: collectionClassName,
+ },
+ ],
+ }
+
+ const libraryTreeRoot = [projectLibrary, communityLibrary]
+
+ set({
+ projectLibraryTreeNodeId: projectLibrary.id,
+ communityLibraryTreeNodeId: communityLibrary.id,
+ libraryTreeRoot,
+ selectedNodeItem: undefined,
+ selectedTreeNodeId: null,
+ })
+ },
+
+ /*
+ setProjectEntities: async (entities: ClapEntity[]) => {
+ const characters: LibraryTreeNode = {
+ id: UUID(),
+ nodeType: 'LIB_NODE_GENERIC_COLLECTION',
+ data: undefined,
+ label: 'Characters',
+ icon: icons.characters,
+ className: collectionClassName,
+ isExpanded: true, // This node is expanded by default
+ children: [],
+ }
+
+ const locations: LibraryTreeNode = {
+ id: UUID(),
+ nodeType: 'LIB_NODE_GENERIC_COLLECTION',
+ data: undefined,
+ label: 'Locations',
+ icon: icons.location,
+ className: collectionClassName,
+ isExpanded: false, // This node is expanded by default
+ children: [],
+ }
+
+ const misc: LibraryTreeNode = {
+ id: UUID(),
+ nodeType: 'LIB_NODE_GENERIC_COLLECTION',
+ data: undefined,
+ label: 'Misc',
+ icon: icons.misc,
+ className: collectionClassName,
+ isExpanded: false, // This node is expanded by default
+ children: [],
+ }
+
+ entities.forEach((entity) => {
+ const node: LibraryTreeNode = {
+ nodeType: TreeNodeEntityItem,
+ id: entity.id,
+ data: entity,
+ label: entity.label,
+ icon: icons.misc,
+ className: itemClassName,
+ }
+ if (entity.category === ClapSegmentCategory.CHARACTER) {
+ node.icon = icons.character
+ characters.children!.push(node)
+ } else if (entity.category === ClapSegmentCategory.LOCATION) {
+ node.icon = icons.location
+ locations.children!.push(node)
+ } else {
+ misc.children!.push(node)
+ }
+ })
+ },
+
+ setCommunityCollections: (collections: CommunityEntityCollection[]) => {
+ // TODO: implement this
+
+ },
+ */
+
+ selectedNodeItem: undefined,
+ selectEntity: (entity?: ClapEntity) => {
+ if (entity) {
+ console.log(
+ 'TODO julian: change this code to search in the entity collections'
+ )
+ const selectedTreeNode = get().libraryTreeRoot.find(
+ (node) => node.data?.id === entity.id
+ )
+
+ // set({ selectedTreeNode })
+ set({ selectedTreeNodeId: selectedTreeNode?.id || null })
+ set({ selectedNodeItem: entity })
+ } else {
+ // set({ selectedTreeNode: undefined })
+ set({ selectedTreeNodeId: null })
+ set({ selectedNodeItem: undefined })
+ }
+ },
+
+ // selectedTreeNode: undefined,
+ selectedTreeNodeId: null,
+ selectTreeNode: (
+ treeNodeId?: string | null,
+ nodeType?: LibraryNodeType,
+ nodeItem?: TreeNodeItem
+ ) => {
+ set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
+ set({ selectedNodeType: nodeType ? nodeType : undefined })
+ set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
+ },
+}))
+
+useEntityTree.getState().init()
diff --git a/src/components/editors/EntityEditor/EntityViewer/EntityList.tsx b/src/components/editors/EntityEditor/EntityViewer/EntityList.tsx
new file mode 100644
index 00000000..5671b93a
--- /dev/null
+++ b/src/components/editors/EntityEditor/EntityViewer/EntityList.tsx
@@ -0,0 +1,65 @@
+import { ClapEntity, ClapSegmentCategory, newEntity } from '@aitube/clap'
+import { useTimeline } from '@aitube/timeline'
+
+import { Button } from '@/components/ui/button'
+import { useEntityEditor, useIO } from '@/services'
+
+export function EntityList({
+ onSelectEntity,
+}: {
+ onSelectEntity: (entityId: string) => void
+}) {
+ const entities = useTimeline((s) => s.entities)
+ const setCurrent = useEntityEditor((s) => s.setCurrent)
+ const addEntity = useEntityEditor((s) => s.addEntity)
+ const removeEntity = useEntityEditor((s) => s.removeEntity)
+
+ const handleAddEntity = () => {
+ const entity: ClapEntity = newEntity({
+ id: Date.now().toString(),
+ label: 'NEW_ENTITY',
+ category: ClapSegmentCategory.CHARACTER,
+ description: '',
+ appearance: '',
+ }) // ignoring some fields for now
+ addEntity(entity)
+ }
+
+ return (
+
+
+
Entities
+
+
+
+ {entities.map((entity: ClapEntity) => (
+ -
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/editors/EntityEditor/EntityViewer/index.tsx b/src/components/editors/EntityEditor/EntityViewer/index.tsx
new file mode 100644
index 00000000..a1d8f359
--- /dev/null
+++ b/src/components/editors/EntityEditor/EntityViewer/index.tsx
@@ -0,0 +1,192 @@
+import { useEffect } from 'react'
+import { ClapEntity, ClapSegmentCategory } from '@aitube/clap'
+import { useTimeline } from '@aitube/timeline'
+
+import { FormFile } from '@/components/forms/FormFile'
+import { FormInput } from '@/components/forms/FormInput'
+import { FormSection } from '@/components/forms/FormSection'
+import { FormSelect } from '@/components/forms/FormSelect'
+import { Button } from '@/components/ui/button'
+import { useEntityEditor, useIO } from '@/services'
+import { cn } from '@/lib/utils'
+
+import { EntityList } from './EntityList'
+
+export function EntityViewer({
+ className = '',
+}: {
+ className?: string
+} = {}) {
+ const entities = useTimeline((s) => s.entities)
+ const updateEntities = useTimeline((s) => s.updateEntities)
+
+ const saveEntitiesToClap = useIO((s) => s.saveEntitiesToClap)
+ const openEntitiesFromClap = useIO((s) => s.openEntitiesFromClap)
+
+ const current = useEntityEditor((s) => s.current)
+ const setCurrent = useEntityEditor((s) => s.setCurrent)
+
+ const draft = useEntityEditor((s) => s.draft)
+ const setDraft = useEntityEditor((s) => s.setDraft)
+
+ const showEntityList = useEntityEditor((s) => s.showEntityList)
+ const setShowEntityList = useEntityEditor((s) => s.setShowEntityList)
+
+ useEffect(() => {
+ setCurrent(entities.at(0))
+ }, [entities, setCurrent])
+
+ useEffect(() => {
+ setDraft(current)
+ }, [current, setDraft])
+
+ const handleInputChange = (
+ field: keyof ClapEntity,
+ value: string | number | undefined
+ ) => {
+ if (!draft) {
+ return
+ }
+ let updatedValue = value
+ if (field === 'age') {
+ updatedValue = value === '' ? undefined : parseInt(value as string)
+ }
+ if (field === 'label') {
+ updatedValue = value?.toString().toUpperCase()
+ }
+
+ setDraft({ ...draft, [field]: updatedValue })
+ }
+
+ const handleSave = () => {
+ if (!draft) {
+ return
+ }
+ updateEntities([draft])
+ }
+
+ const handleFileUpload = async (field: 'imageId' | 'audioId', file: File) => {
+ if (!draft) {
+ return
+ }
+ const dataUrl = await new Promise((resolve) => {
+ const reader = new FileReader()
+ reader.onload = (e) => resolve(e.target?.result as string)
+ reader.readAsDataURL(file)
+ })
+ setDraft({ ...draft, [field]: dataUrl })
+ }
+
+ const handleExport = async () => {
+ if (!draft) {
+ return
+ }
+ await saveEntitiesToClap([draft])
+ }
+
+ const handleImport = async (file: File) => {
+ await openEntitiesFromClap(file)
+ }
+
+ const handleBack = () => {
+ setShowEntityList(true)
+ }
+
+ const handleSelectEntity = (entityId: string) => {
+ setShowEntityList(false)
+ }
+
+ return (
+
+ {showEntityList ? (
+
+
+
+ ) : (
+
+ {draft && (
+
+
+ handleInputChange('label', value)}
+ />
+
+ label="Category"
+ selectedItemId={draft.category}
+ items={Object.values(ClapSegmentCategory).map(
+ (category: ClapSegmentCategory) => ({
+ id: category,
+ label: category,
+ value: category,
+ })
+ )}
+ onSelect={(value) => handleInputChange('category', value)}
+ />
+ {/* ... form fields ... */}
+
+ files[0] && handleFileUpload('imageId', files[0])
+ }
+ />
+ {draft.imageId && (
+
+
+
+ )}
+
+ files[0] && handleFileUpload('audioId', files[0])
+ }
+ />
+ {draft.audioId && (
+
+ )}
+ handleInputChange('description', value)}
+ />
+ handleInputChange('appearance', value)}
+ />
+ handleInputChange('age', value)}
+ />
+ handleInputChange('gender', value)}
+ />
+
+
+
+
+
+ files[0] && handleImport(files[0])}
+ />
+
+
+ )}
+
+ )}
+
+ )
+}
diff --git a/src/components/editors/EntityEditor/index.tsx b/src/components/editors/EntityEditor/index.tsx
index 5787833a..026957a6 100644
--- a/src/components/editors/EntityEditor/index.tsx
+++ b/src/components/editors/EntityEditor/index.tsx
@@ -1,243 +1,17 @@
-import { useEffect } from 'react'
-import { ClapEntity, ClapSegmentCategory, newEntity } from '@aitube/clap'
-import { useTimeline } from '@aitube/timeline'
-
-import { FormFile } from '@/components/forms/FormFile'
-import { FormInput } from '@/components/forms/FormInput'
-import { FormSection } from '@/components/forms/FormSection'
-import { FormSelect } from '@/components/forms/FormSelect'
-import { Button } from '@/components/ui/button'
-import { useEntityEditor, useIO } from '@/services'
-
-function EntityList({
- onSelectEntity,
-}: {
- onSelectEntity: (entityId: string) => void
-}) {
- const entities = useTimeline((s) => s.entities)
- const setCurrent = useEntityEditor((s) => s.setCurrent)
- const addEntity = useEntityEditor((s) => s.addEntity)
- const removeEntity = useEntityEditor((s) => s.removeEntity)
-
- const handleAddEntity = () => {
- const entity: ClapEntity = newEntity({
- id: Date.now().toString(),
- label: 'NEW_ENTITY',
- category: ClapSegmentCategory.CHARACTER,
- description: '',
- appearance: '',
- }) // ignoring some fields for now
- addEntity(entity)
- }
-
- return (
-
-
-
Entities
-
-
-
- {entities.map((entity: ClapEntity) => (
- -
-
-
-
- ))}
-
-
- )
-}
+import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'
+import { EntityTree } from './EntityTree'
+import { EntityViewer } from './EntityViewer'
export function EntityEditor() {
- const entities = useTimeline((s) => s.entities)
- const updateEntities = useTimeline((s) => s.updateEntities)
-
- const saveEntitiesToClap = useIO((s) => s.saveEntitiesToClap)
- const openEntitiesFromClap = useIO((s) => s.openEntitiesFromClap)
-
- const current = useEntityEditor((s) => s.current)
- const setCurrent = useEntityEditor((s) => s.setCurrent)
-
- const draft = useEntityEditor((s) => s.draft)
- const setDraft = useEntityEditor((s) => s.setDraft)
-
- const showEntityList = useEntityEditor((s) => s.showEntityList)
- const setShowEntityList = useEntityEditor((s) => s.setShowEntityList)
-
- useEffect(() => {
- setCurrent(entities.at(0))
- }, [entities, setCurrent])
-
- useEffect(() => {
- setDraft(current)
- }, [current, setDraft])
-
- const handleInputChange = (
- field: keyof ClapEntity,
- value: string | number | undefined
- ) => {
- if (!draft) {
- return
- }
- let updatedValue = value
- if (field === 'age') {
- updatedValue = value === '' ? undefined : parseInt(value as string)
- }
- if (field === 'label') {
- updatedValue = value?.toString().toUpperCase()
- }
-
- setDraft({ ...draft, [field]: updatedValue })
- }
-
- const handleSave = () => {
- if (!draft) {
- return
- }
- updateEntities([draft])
- }
-
- const handleFileUpload = async (field: 'imageId' | 'audioId', file: File) => {
- if (!draft) {
- return
- }
- const dataUrl = await new Promise((resolve) => {
- const reader = new FileReader()
- reader.onload = (e) => resolve(e.target?.result as string)
- reader.readAsDataURL(file)
- })
- setDraft({ ...draft, [field]: dataUrl })
- }
-
- const handleExport = async () => {
- if (!draft) {
- return
- }
- await saveEntitiesToClap([draft])
- }
-
- const handleImport = async (file: File) => {
- await openEntitiesFromClap(file)
- }
-
- const handleBack = () => {
- setShowEntityList(true)
- }
-
- const handleSelectEntity = (entityId: string) => {
- setShowEntityList(false)
- }
-
return (
-
- {showEntityList ? (
-
-
-
- ) : (
-
- {draft && (
-
-
- handleInputChange('label', value)}
- />
-
- label="Category"
- selectedItemId={draft.category}
- items={Object.values(ClapSegmentCategory).map(
- (category: ClapSegmentCategory) => ({
- id: category,
- label: category,
- value: category,
- })
- )}
- onSelect={(value) => handleInputChange('category', value)}
- />
- {/* ... form fields ... */}
-
- files[0] && handleFileUpload('imageId', files[0])
- }
- />
- {draft.imageId && (
-
-
-
- )}
-
- files[0] && handleFileUpload('audioId', files[0])
- }
- />
- {draft.audioId && (
-
- )}
- handleInputChange('description', value)}
- />
- handleInputChange('appearance', value)}
- />
- handleInputChange('age', value)}
- />
- handleInputChange('gender', value)}
- />
-
-
-
-
-
- files[0] && handleImport(files[0])}
- />
-
-
- )}
-
- )}
-
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/components/editors/WorkflowEditor/WorkflowTree/index.tsx b/src/components/editors/WorkflowEditor/WorkflowTree/index.tsx
new file mode 100644
index 00000000..69ed1cca
--- /dev/null
+++ b/src/components/editors/WorkflowEditor/WorkflowTree/index.tsx
@@ -0,0 +1,63 @@
+'use client'
+
+import { cn } from '@/lib/utils'
+import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
+import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
+import { Tree } from '@/components/core/tree'
+
+import { useWorkflowTree } from './useWorkflowTree'
+
+export function WorkflowTree({
+ className = '',
+}: {
+ className?: string
+} = {}) {
+ const libraryTreeRoot = useWorkflowTree((s) => s.libraryTreeRoot)
+ const selectTreeNode = useWorkflowTree((s) => s.selectTreeNode)
+ const selectedTreeNodeId = useWorkflowTree((s) => s.selectedTreeNodeId)
+
+ /**
+ * handle click on tree node
+ * yes, this is where the magic happens!
+ *
+ * @param id
+ * @param nodeType
+ * @param node
+ * @returns
+ */
+ const handleOnChange = async (
+ id: string | null,
+ nodeType?: LibraryNodeType,
+ nodeItem?: TreeNodeItem
+ ) => {
+ console.log(`calling selectTreeNodeById(id)`)
+ selectTreeNode(id, nodeType, nodeItem)
+
+ if (!nodeType || !nodeItem) {
+ console.log('tree-browser: clicked on an undefined node')
+ return
+ }
+ if (isClapEntity(nodeType, nodeItem)) {
+ // ClapEntity
+ } else {
+ console.log(
+ `tree-browser: no action attached to ${nodeType}, so skipping`
+ )
+ return
+ }
+ console.log(`tree-browser: clicked on a ${nodeType}`, nodeItem)
+ }
+
+ return (
+
+ value={selectedTreeNodeId}
+ onChange={handleOnChange}
+ className={cn(`not-prose h-full w-full px-2 pt-2`, className)}
+ label="Workflows"
+ >
+ {libraryTreeRoot.map((node) => (
+
+ ))}
+
+ )
+}
diff --git a/src/components/tree-browsers/stores/useWorkflowLibrary.ts b/src/components/editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts
similarity index 73%
rename from src/components/tree-browsers/stores/useWorkflowLibrary.ts
rename to src/components/editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts
index 363adcba..cb8e927b 100644
--- a/src/components/tree-browsers/stores/useWorkflowLibrary.ts
+++ b/src/components/editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts
@@ -2,12 +2,20 @@
import { create } from 'zustand'
import { ClapEntity, UUID } from '@aitube/clap'
-import { LibraryNodeItem, LibraryNodeType, LibraryTreeNode } from '../types'
+
import { icons } from '@/components/icons'
-import { collectionClassName, libraryClassName } from './treeNodeStyles'
+import {
+ TreeNodeItem,
+ LibraryNodeType,
+ LibraryTreeNode,
+} from '@/components/tree-browsers/types'
+import {
+ collectionClassName,
+ libraryClassName,
+} from '@/components/tree-browsers/style/treeNodeStyles'
-export const useWorkflowLibrary = create<{
- builtInLibraryTreeNodeId: string
+export const useWorkflowTree = create<{
+ builtinLibraryTreeNodeId: string
communityLibraryTreeNodeId: string
libraryTreeRoot: LibraryTreeNode[]
init: () => void
@@ -29,32 +37,32 @@ export const useWorkflowLibrary = create<{
//setCommunityCollections: (collections: WorkflowCollection[]) => void
// we support those all selection modes for convenience - please keep them!
- selectedNodeItem?: LibraryNodeItem
+ selectedNodeItem?: TreeNodeItem
selectedNodeType?: LibraryNodeType
selectTreeNode: (
treeNodeId?: string | null,
nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
+ nodeItem?: TreeNodeItem
) => void
selectedTreeNodeId: string | null
}>((set, get) => ({
- builtInLibraryTreeNodeId: '',
+ builtinLibraryTreeNodeId: '',
communityLibraryTreeNodeId: '',
libraryTreeRoot: [],
init: () => {
- const builtInLibrary: LibraryTreeNode = {
+ const builtinLibrary: LibraryTreeNode = {
id: UUID(),
- nodeType: 'LIB_NODE_WORKFLOWS',
- label: 'Built-in workflows',
- icon: icons.project,
+ nodeType: 'TREE_ROOT_BUILTIN',
+ label: 'Default workflows',
+ icon: icons.community,
className: libraryClassName,
isExpanded: true,
children: [
{
id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
- label: 'A - 2',
- icon: icons.project,
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
+ label: 'Empty',
+ icon: icons.community,
className: collectionClassName,
},
],
@@ -62,25 +70,25 @@ export const useWorkflowLibrary = create<{
const communityLibrary: LibraryTreeNode = {
id: UUID(),
- nodeType: 'LIB_NODE_COMMUNITY_COLLECTION',
+ nodeType: 'TREE_ROOT_COMMUNITY',
label: 'Community workflows',
icon: icons.community,
className: libraryClassName,
children: [
{
id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
- label: 'A - 2',
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
+ label: 'Empty',
icon: icons.community,
className: collectionClassName,
},
],
}
- const libraryTreeRoot = [builtInLibrary, communityLibrary]
+ const libraryTreeRoot = [builtinLibrary, communityLibrary]
set({
- builtInLibraryTreeNodeId: builtInLibrary.id,
+ builtinLibraryTreeNodeId: builtinLibrary.id,
communityLibraryTreeNodeId: communityLibrary.id,
libraryTreeRoot,
selectedNodeItem: undefined,
@@ -113,7 +121,7 @@ export const useWorkflowLibrary = create<{
selectTreeNode: (
treeNodeId?: string | null,
nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
+ nodeItem?: TreeNodeItem
) => {
set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
set({ selectedNodeType: nodeType ? nodeType : undefined })
@@ -121,4 +129,4 @@ export const useWorkflowLibrary = create<{
},
}))
-useWorkflowLibrary.getState().init()
+useWorkflowTree.getState().init()
diff --git a/src/components/editors/WorkflowEditor/viewer/NodeView.tsx b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/NodeView.tsx
similarity index 97%
rename from src/components/editors/WorkflowEditor/viewer/NodeView.tsx
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/NodeView.tsx
index 5f9cb6ad..723ff7b6 100644
--- a/src/components/editors/WorkflowEditor/viewer/NodeView.tsx
+++ b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/NodeView.tsx
@@ -1,9 +1,9 @@
import React, { memo } from 'react'
import { Handle, Position } from '@xyflow/react'
-import { ReactWorkflowNode } from '../types'
import { useTheme } from '@/services'
import { cn } from '@/lib/utils'
+import { ReactWorkflowNode } from './types'
function NodeComponent({ data }: ReactWorkflowNode) {
const theme = useTheme()
diff --git a/src/components/editors/WorkflowEditor/clapWorkflowToReactWorkflow.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts
similarity index 82%
rename from src/components/editors/WorkflowEditor/clapWorkflowToReactWorkflow.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts
index 03278e5c..41349883 100644
--- a/src/components/editors/WorkflowEditor/clapWorkflowToReactWorkflow.ts
+++ b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts
@@ -1,7 +1,7 @@
import { ClapWorkflow, ClapWorkflowEngine } from '@aitube/clap'
import { ReactWorkflow } from './types'
-import { glifToReactWorkflow } from './specialized/glif/glifToReactWorkflow'
+import { glifToReactWorkflow } from './formats/glif/glifToReactWorkflow'
export function clapWorkflowToReactWorkflow(
clapWorkflow: ClapWorkflow
diff --git a/src/components/editors/WorkflowEditor/specialized/comfyui/types.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/comfyui/types.ts
similarity index 100%
rename from src/components/editors/WorkflowEditor/specialized/comfyui/types.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/comfyui/types.ts
diff --git a/src/components/editors/WorkflowEditor/specialized/falai/types.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/falai/types.ts
similarity index 100%
rename from src/components/editors/WorkflowEditor/specialized/falai/types.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/falai/types.ts
diff --git a/src/components/editors/WorkflowEditor/specialized/glif/glifToReactWorkflow.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/glif/glifToReactWorkflow.ts
similarity index 100%
rename from src/components/editors/WorkflowEditor/specialized/glif/glifToReactWorkflow.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/glif/glifToReactWorkflow.ts
diff --git a/src/components/editors/WorkflowEditor/specialized/glif/types.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/glif/types.ts
similarity index 100%
rename from src/components/editors/WorkflowEditor/specialized/glif/types.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/formats/glif/types.ts
diff --git a/src/components/editors/WorkflowEditor/viewer/WorkflowView.tsx b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/index.tsx
similarity index 56%
rename from src/components/editors/WorkflowEditor/viewer/WorkflowView.tsx
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/index.tsx
index a0f0583c..2ecc73ef 100644
--- a/src/components/editors/WorkflowEditor/viewer/WorkflowView.tsx
+++ b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/index.tsx
@@ -14,18 +14,18 @@ import {
import '@xyflow/react/dist/base.css'
import { NodeView } from './NodeView'
-import { ReactWorkflowEdge, ReactWorkflowNode } from '../types'
+import { ReactWorkflowEdge, ReactWorkflowNode } from './types'
import { useWorkflowEditor } from '@/services/editors'
-import { glifs } from '../samples/glif'
-import { glifToReactWorkflow } from '../specialized/glif/glifToReactWorkflow'
+import { glifs } from './samples/glif'
+import { glifToReactWorkflow } from './formats/glif/glifToReactWorkflow'
import { useTheme } from '@/services'
const nodeTypes = {
custom: NodeView,
}
-export function WorkflowView() {
+export function ReactFlowCanvas() {
const theme = useTheme()
const current = useWorkflowEditor((s) => s.current)
const [nodes, setNodes, onNodesChange] = useNodesState([])
@@ -59,8 +59,27 @@ export function WorkflowView() {
backgroundColor:
theme.workflow.bgColor || theme.defaultBgColor || '#000000',
}}
+ proOptions={{
+ // Since a workflow can be shown inside a tiny panel, we need to free up visual space.
+ // As a consequence I have hidden the React Flow attribution.
+ // Doing so is acceptable by their terms, since at the time of writing
+ // Clapper is a non-commercial project that doesn't belong to any organization.
+ //
+ // for more information about React Flow licensing and attribution:
+ //
+ // "If you start making money using React Flow or use it in an organization in the future,
+ // we would ask that you re-add the attribution or sign up for one of our subscriptions."
+ //
+ // https://reactflow.dev/learn/troubleshooting/remove-attribution
+ hideAttribution: true,
+ }}
>
-
+
)
diff --git a/src/components/editors/WorkflowEditor/samples/glif.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/samples/glif.ts
similarity index 99%
rename from src/components/editors/WorkflowEditor/samples/glif.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/samples/glif.ts
index fbf3175e..5c53501f 100644
--- a/src/components/editors/WorkflowEditor/samples/glif.ts
+++ b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/samples/glif.ts
@@ -1,4 +1,4 @@
-import { GlifWorkflow } from '../specialized/glif/types'
+import { GlifWorkflow } from '../formats/glif/types'
export const glifs: GlifWorkflow[] = [
{
diff --git a/src/components/editors/WorkflowEditor/types.ts b/src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/types.ts
similarity index 100%
rename from src/components/editors/WorkflowEditor/types.ts
rename to src/components/editors/WorkflowEditor/WorkflowViewer/ReactFlowCanvas/types.ts
diff --git a/src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx b/src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx
new file mode 100644
index 00000000..6f10f2a9
--- /dev/null
+++ b/src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx
@@ -0,0 +1,31 @@
+import { FormSection } from '@/components/forms/FormSection'
+import { useWorkflowEditor } from '@/services/editors'
+import { useUI } from '@/services'
+import { ReactFlowCanvas } from './ReactFlowCanvas'
+import { cn } from '@/lib/utils'
+
+export function WorkflowViewer({
+ className = '',
+}: {
+ className?: string
+} = {}) {
+ const current = useWorkflowEditor((s) => s.current)
+ const hasBetaAccess = useUI((s) => s.hasBetaAccess)
+
+ const content = hasBetaAccess ? (
+
+ ) : !current ? (
+
+ Workflows are not implemented yet.
+
+ ) : (
+
+ Should be a form to edit the parameters.
+
+ We can also display a link or an iframe with the actual workflow graph.
+
+
+ )
+
+ return {content}
+}
diff --git a/src/components/editors/WorkflowEditor/index.tsx b/src/components/editors/WorkflowEditor/index.tsx
index f9f1d3e1..eac6401b 100644
--- a/src/components/editors/WorkflowEditor/index.tsx
+++ b/src/components/editors/WorkflowEditor/index.tsx
@@ -1,37 +1,17 @@
-import { useEffect } from 'react'
-
-import { FormInput } from '@/components/forms/FormInput'
-import { FormSection } from '@/components/forms/FormSection'
-import { useWorkflowEditor } from '@/services/editors'
-import { useUI } from '@/services'
-import { WorkflowView } from './viewer/WorkflowView'
+import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'
+import { WorkflowTree } from './WorkflowTree'
+import { WorkflowViewer } from './WorkflowViewer'
export function WorkflowEditor() {
- const current = useWorkflowEditor((s) => s.current)
- const setCurrent = useWorkflowEditor((s) => s.setCurrent)
- const history = useWorkflowEditor((s) => s.history)
- const undo = useWorkflowEditor((s) => s.undo)
- const redo = useWorkflowEditor((s) => s.redo)
-
- const hasBetaAccess = useUI((s) => s.hasBetaAccess)
-
- if (hasBetaAccess) {
- return
- }
- if (!current) {
- return (
-
- Workflows are not implemented yet.
-
- )
- }
-
return (
-
- Should be a form to edit the parameters.
-
- We can also display a link or an iframe with the actual workflow graph.
-
-
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/components/editors/WorkflowEditor/viewer/README.md b/src/components/editors/WorkflowEditor/viewer/README.md
deleted file mode 100644
index dd049b13..00000000
--- a/src/components/editors/WorkflowEditor/viewer/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Workflow
diff --git a/src/components/forms/index.ts b/src/components/forms/index.ts
new file mode 100644
index 00000000..d4190c06
--- /dev/null
+++ b/src/components/forms/index.ts
@@ -0,0 +1,9 @@
+export { FormDir } from './FormDir'
+export { FormField } from './FormField'
+export { FormFile } from './FormFile'
+export { FormInput } from './FormInput'
+export { FormLabel } from './FormLabel'
+export { FormRadio } from './FormRadio'
+export { FormSection } from './FormSection'
+export { FormSelect } from './FormSelect'
+export { FormSwitch } from './FormSwitch'
diff --git a/src/components/toolbars/top-bar/index.tsx b/src/components/toolbars/top-bar/index.tsx
index 417dd6b1..ec407e85 100644
--- a/src/components/toolbars/top-bar/index.tsx
+++ b/src/components/toolbars/top-bar/index.tsx
@@ -1,24 +1,31 @@
-import { ClapProject } from '@aitube/clap'
-import { useTimeline } from '@aitube/timeline'
+'use client'
+import { useTheme } from '@/services'
import { cn } from '@/lib/utils'
import { TopMenu } from '../top-menu'
export function TopBar() {
- const clap: ClapProject = useTimeline((s) => s.clap)
+ const theme = useTheme()
return (
)
}
diff --git a/src/components/toolbars/top-menu/index.tsx b/src/components/toolbars/top-menu/index.tsx
index 2c08acd3..87b719ff 100644
--- a/src/components/toolbars/top-menu/index.tsx
+++ b/src/components/toolbars/top-menu/index.tsx
@@ -40,7 +40,7 @@ export function TopMenu() {
return (
{
setIsTopMenuOpen(!!value)
}}
diff --git a/src/components/tree-browsers/model-tree-browser/index.tsx b/src/components/tree-browsers/model-tree-browser/index.tsx
deleted file mode 100644
index 47705a93..00000000
--- a/src/components/tree-browsers/model-tree-browser/index.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-'use client'
-
-import { useEffect } from 'react'
-
-import { cn } from '@/lib/utils'
-import { useEntityLibrary } from '../stores/useEntityLibrary'
-import { LibraryNodeItem, LibraryNodeType } from '../types'
-import { Tree } from '@/components/core/tree'
-
-import { isClapEntity, isReplicateCollection } from '../utils/isSomething'
-import { useCivitaiCollections } from '../stores/useCivitaiCollections'
-import { useReplicateCollections } from '../stores/useReplicateCollections'
-
-export function ModelTreeBrowser() {
- const libraryTreeRoot = useEntityLibrary((s) => s.libraryTreeRoot)
- const selectTreeNode = useEntityLibrary((s) => s.selectTreeNode)
- const selectedTreeNodeId = useEntityLibrary((s) => s.selectedTreeNodeId)
- const setReplicateCollections = useEntityLibrary(
- (s) => s.setReplicateCollections
- )
- const setCivitaiCollections = useEntityLibrary((s) => s.setCivitaiCollections)
-
- // TODO: we are forced to do this because the api "endpoint" is a server action
- // however we could rewrite it so that we can pull the collections directly
- // from the Zustand store
-
- const newReplicateCollections = useReplicateCollections()
- useEffect(() => {
- setReplicateCollections(newReplicateCollections)
- // eslint-disable-next-line
- }, [JSON.stringify(newReplicateCollections)]) // ... yeah, I know, I know..
-
- const newCivitaiCollections = useCivitaiCollections()
- useEffect(() => {
- setCivitaiCollections(newCivitaiCollections)
- // eslint-disable-next-line
- }, [JSON.stringify(newCivitaiCollections)]) // ... yeah, I know, I know..
-
- /**
- * handle click on tree node
- * yes, this is where the magic happens!
- *
- * @param id
- * @param nodeType
- * @param node
- * @returns
- */
- const handleOnChange = async (
- id: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => {
- console.log(`calling selectTreeNodeById(id)`)
- selectTreeNode(id, nodeType, nodeItem)
-
- if (!nodeType || !nodeItem) {
- console.log('tree-browser: clicked on an undefined node')
- return
- }
-
- if (isReplicateCollection(nodeType, nodeItem)) {
- // ReplicateCollection
- } else if (isClapEntity(nodeType, nodeItem)) {
- // ClapEntity
- } else {
- console.log(
- `tree-browser: no action attached to ${nodeType}, so skipping`
- )
- return
- }
- console.log(`tree-browser: clicked on a ${nodeType}`, nodeItem)
- }
-
- return (
-
-
- value={selectedTreeNodeId}
- onChange={handleOnChange}
- className="not-prose h-full w-full px-2 pt-8"
- label="Model Library"
- >
- {libraryTreeRoot.map((node) => (
-
- ))}
-
-
- )
-}
diff --git a/src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx b/src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx
deleted file mode 100644
index d35e4752..00000000
--- a/src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-'use client'
-
-import { useEntityLibrary } from '../stores/useEntityLibrary'
-
-export function TreeItemViewer() {
- const selectedNodeItem = useEntityLibrary((s) => s.selectedNodeItem)
- const selectedNodeType = useEntityLibrary((s) => s.selectedNodeType)
-
- const nodeType = selectedNodeType
- const data = selectedNodeItem
-
- if (!nodeType || !data) {
- return null
- }
-
- return The selected item cannot be preview.
-}
diff --git a/src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx b/src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx
deleted file mode 100644
index e0d8d324..00000000
--- a/src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-'use client'
-
-import { useEntityLibrary } from '../stores/useEntityLibrary'
-
-export function TreeItemViewer() {
- const selectedNodeItem = useEntityLibrary((s) => s.selectedNodeItem)
- const selectedNodeType = useEntityLibrary((s) => s.selectedNodeType)
-
- const nodeType = selectedNodeType
- const data = selectedNodeItem
-
- if (!nodeType || !data) {
- return null
- }
-
- return TODO
-}
diff --git a/src/components/tree-browsers/stores/treeNodeStyles.ts b/src/components/tree-browsers/stores/treeNodeStyles.ts
deleted file mode 100644
index 86468745..00000000
--- a/src/components/tree-browsers/stores/treeNodeStyles.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// TODO: this isn't the best place for this as this is style,
-// and we are in a state manager
-export const libraryClassName = 'text-base font-semibold'
-
-export const collectionClassName = `text-base font-normal`
-
-export const itemClassName =
- 'text-sm font-light text-gray-200/60 hover:text-gray-200/100'
diff --git a/src/components/tree-browsers/stores/useCivitaiCollections.ts b/src/components/tree-browsers/stores/useCivitaiCollections.ts
deleted file mode 100644
index 8b9e0998..00000000
--- a/src/components/tree-browsers/stores/useCivitaiCollections.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client'
-
-import { useEffect, useState, useTransition } from 'react'
-import { CivitaiCollection } from '../types'
-
-export function useCivitaiCollections(): CivitaiCollection[] {
- const [_pending, startTransition] = useTransition()
- const [collections, setCollections] = useState([])
- // const [models, setModels] = useState([])
-
- useEffect(() => {
- startTransition(async () => {
- // TODO @Julian: this was something we did in the ligacy
- // Clapper, but I'm not sure we want to support Civitai
- // again just yet, we probably require other arch changes
- // const collections = await listCollections()
- const collections: CivitaiCollection[] = []
- setCollections(collections)
- // setModels(models)
- })
- }, [])
-
- return collections
-}
diff --git a/src/components/tree-browsers/stores/useEntityLibrary.ts b/src/components/tree-browsers/stores/useEntityLibrary.ts
deleted file mode 100644
index 53d241c1..00000000
--- a/src/components/tree-browsers/stores/useEntityLibrary.ts
+++ /dev/null
@@ -1,336 +0,0 @@
-'use client'
-
-import { create } from 'zustand'
-import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap'
-import {
- CivitaiCollection,
- LibraryNodeItem,
- LibraryNodeType,
- LibraryTreeNode,
- ReplicateCollection,
-} from '../types'
-import { icons } from '@/components/icons'
-import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
-import {
- collectionClassName,
- itemClassName,
- libraryClassName,
-} from './treeNodeStyles'
-
-export const useEntityLibrary = create<{
- teamLibraryTreeNodeId: string
- communityLibraryTreeNodeId: string
- civitaiLibraryTreeNodeId: string
- huggingfaceLibraryTreeNodeId: string
- replicateLibraryTreeNodeId: string
- libraryTreeRoot: LibraryTreeNode[]
- init: () => void
-
- /**
- * Load Replicate collections (API models) into the tree
- *
- * @param collections
- * @returns
- */
- setReplicateCollections: (collections: ReplicateCollection[]) => void
-
- /**
- * Load Replicate collections (LoRA models) into the tree
- *
- * @param collections
- * @returns
- */
- setCivitaiCollections: (collections: CivitaiCollection[]) => void
-
- // we support those all selection modes for convenience - please keep them!
- selectedNodeItem?: LibraryNodeItem
- selectedNodeType?: LibraryNodeType
- selectTreeNode: (
- treeNodeId?: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => void
- selectedTreeNodeId: string | null
-}>((set, get) => ({
- localUserLibraryTreeNodeId: '',
- huggingfaceUserLibraryTreeNodeId: '',
- teamLibraryTreeNodeId: '',
- communityLibraryTreeNodeId: '',
- civitaiLibraryTreeNodeId: '',
- huggingfaceLibraryTreeNodeId: '',
- replicateLibraryTreeNodeId: '',
- libraryTreeRoot: [],
- init: () => {
- const teamLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_TEAM_COLLECTION',
- label: 'Team models',
- icon: icons.team,
- className: libraryClassName,
- isExpanded: true,
- children: [
- {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
- label: 'A - 2',
- icon: icons.team,
- className: collectionClassName,
- },
- ],
- }
-
- const civitaiLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_CIVITAI_COLLECTION',
- label: 'Civitai models',
- icon: icons.community,
- className: libraryClassName,
- children: [],
- }
-
- const communityLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_COMMUNITY_COLLECTION',
- label: 'Community models',
- icon: icons.community,
- className: libraryClassName,
- children: [
- {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
- label: 'A - 2',
- icon: icons.community,
- className: collectionClassName,
- },
- ],
- }
-
- const huggingfaceLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_HUGGINGFACE_COLLECTION',
- label: 'Hugging Face',
- icon: icons.vendor,
- className: libraryClassName,
- children: [],
- }
-
- const replicateLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_REPLICATE_COLLECTION',
- label: 'Replicate',
- icon: icons.vendor,
- isExpanded: false, // This node is expanded by default
- className: libraryClassName,
- children: [],
- }
-
- const libraryTreeRoot = [
- // teamLibrary,
- // communityLibrary,
- civitaiLibrary,
- // huggingfaceLibrary,
- replicateLibrary,
- ]
-
- set({
- teamLibraryTreeNodeId: teamLibrary.id,
- civitaiLibraryTreeNodeId: civitaiLibrary.id,
- communityLibraryTreeNodeId: communityLibrary.id,
- huggingfaceLibraryTreeNodeId: huggingfaceLibrary.id,
- replicateLibraryTreeNodeId: replicateLibrary.id,
- libraryTreeRoot,
- selectedNodeItem: undefined,
- selectedTreeNodeId: null,
- })
- },
-
- setProjectEntities: async (entities: ClapEntity[]) => {
- const characters: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Characters',
- icon: icons.characters,
- className: collectionClassName,
- isExpanded: true, // This node is expanded by default
- children: [],
- }
-
- const locations: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Locations',
- icon: icons.location,
- className: collectionClassName,
- isExpanded: false, // This node is expanded by default
- children: [],
- }
-
- const misc: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Misc',
- icon: icons.misc,
- className: collectionClassName,
- isExpanded: false, // This node is expanded by default
- children: [],
- }
-
- entities.forEach((entity) => {
- const node: LibraryTreeNode = {
- nodeType: 'LIB_NODE_REPLICATE_MODEL',
- id: entity.id,
- data: entity,
- label: entity.label,
- icon: icons.misc,
- className: itemClassName,
- }
- if (entity.category === ClapSegmentCategory.CHARACTER) {
- node.icon = icons.character
- characters.children!.push(node)
- } else if (entity.category === ClapSegmentCategory.LOCATION) {
- node.icon = icons.location
- locations.children!.push(node)
- } else {
- misc.children!.push(node)
- }
- })
- },
-
- setReplicateCollections: (collections: ReplicateCollection[]) => {
- const { replicateLibraryTreeNodeId, libraryTreeRoot } = get()
-
- set({
- libraryTreeRoot: libraryTreeRoot.map((node) => {
- if (node.id !== replicateLibraryTreeNodeId) {
- return node
- }
-
- return {
- ...node,
-
- children:
- // only keep non-empty models
- collections
- .filter((c) => c.models.length)
-
- // only visual or sound oriented models
- .filter((c) => {
- const name = c.name.toLowerCase()
-
- // ignore captioning models, we don't need this right now
- if (name.includes('to text') || name.includes('to-text')) {
- return false
- }
-
- if (
- name.includes('image') ||
- name.includes('video') ||
- name.includes('style') ||
- name.includes('audio') ||
- name.includes('sound') ||
- name.includes('music') ||
- name.includes('speech') ||
- name.includes('voice') ||
- name.includes('resolution') ||
- name.includes('upscale') ||
- name.includes('upscaling') ||
- name.includes('interpolate') ||
- name.includes('interpolation')
- ) {
- return true
- }
- return false
- })
- .map((c) => ({
- id: UUID(),
- data: c,
- nodeType: 'LIB_NODE_REPLICATE_COLLECTION',
- label: c.name,
- icon: getAppropriateIcon(c.name),
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
- isExpanded: false, // This node is expanded by default
- children: c.models.map((m) => ({
- nodeType: 'LIB_NODE_REPLICATE_MODEL',
- id: m.id,
- data: m,
- label: m.label,
- icon: getAppropriateIcon(m.label, getAppropriateIcon(c.name)),
- className: itemClassName, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
- })),
- })),
- }
- }),
- })
- },
-
- setCivitaiCollections: (collections: CivitaiCollection[]) => {
- const { civitaiLibraryTreeNodeId, libraryTreeRoot } = get()
-
- set({
- libraryTreeRoot: libraryTreeRoot.map((node) => {
- if (node.id !== civitaiLibraryTreeNodeId) {
- return node
- }
-
- return {
- ...node,
-
- children: collections.map((c) => ({
- id: UUID(),
- data: c,
- nodeType: 'LIB_NODE_CIVITAI_COLLECTION',
- label: c.name,
- icon: getAppropriateIcon(c.name),
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
- isExpanded: false, // This node is expanded by default
- children: c.models.map((m) => ({
- nodeType: 'LIB_NODE_CIVITAI_MODEL',
- id: m.id,
- data: m,
- label: m.label,
- icon: getAppropriateIcon(m.label, getAppropriateIcon(c.name)),
- className: itemClassName, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
- })),
- })),
- }
- }),
- })
- },
-
- selectedNodeItem: undefined,
- selectEntity: (entity?: ClapEntity) => {
- if (entity) {
- console.log(
- 'TODO julian: change this code to search in the entity collections'
- )
- const selectedTreeNode = get().libraryTreeRoot.find(
- (node) => node.data?.id === entity.id
- )
-
- // set({ selectedTreeNode })
- set({ selectedTreeNodeId: selectedTreeNode?.id || null })
- set({ selectedNodeItem: entity })
- } else {
- // set({ selectedTreeNode: undefined })
- set({ selectedTreeNodeId: null })
- set({ selectedNodeItem: undefined })
- }
- },
-
- // selectedTreeNode: undefined,
- selectedTreeNodeId: null,
- selectTreeNode: (
- treeNodeId?: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => {
- set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
- set({ selectedNodeType: nodeType ? nodeType : undefined })
- set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
- },
-}))
-
-useEntityLibrary.getState().init()
diff --git a/src/components/tree-browsers/stores/useFileLibrary.txt b/src/components/tree-browsers/stores/useFileLibrary.txt
deleted file mode 100644
index da935382..00000000
--- a/src/components/tree-browsers/stores/useFileLibrary.txt
+++ /dev/null
@@ -1,204 +0,0 @@
-"use client"
-
-import { create } from "zustand"
-
-import { ClapEntity, UUID } from "@aitube/clap"
-import { HuggingFaceUserCollection, LibraryNodeItem, LibraryNodeType, LibraryTreeNode, LocalUserCollection } from "../types"
-import { icons } from "@/components/icons"
-import { getAppropriateIcon } from "@/components/icons/getAppropriateIcon"
-import { className } from "@/app/fonts"
-import { getCollectionItemTextColor } from "../utils/getCollectionItemTextColor"
-import { collectionClassName, itemClassName, libraryClassName } from './treeNodeStyles'
-
-export const useFileLibrary = create<{
- localUserLibraryTreeNodeId: string
- huggingfaceUserLibraryTreeNodeId: string
- libraryTreeRoot: LibraryTreeNode[]
- init: () => void
-
- /**
- * Load local user collections (projects, assets) into the tree
- *
- * @param collections
- * @returns
- */
- setLocalUserCollections: (collections: LocalUserCollection[]) => void
-
- /**
- * Load Hugging Face user collections (projects, assets) into the tree
- *
- * @param collections
- * @returns
- */
- setHuggingFaceUserCollections: (collections: HuggingFaceUserCollection[]) => void
-
- // we support those all selection modes for convenience - please keep them!
- selectedNodeItem?: LibraryNodeItem
- selectedNodeType?: LibraryNodeType
- selectTreeNode: (treeNodeId?: string | null, nodeType?: LibraryNodeType, nodeItem?: LibraryNodeItem) => void
- selectedTreeNodeId: string | null
-}>((set, get) => ({
- localUserLibraryTreeNodeId: "",
- huggingfaceUserLibraryTreeNodeId: "",
- libraryTreeRoot: [],
- init: () => {
-
- const localUserLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: "LIB_NODE_LOCAL_USER_COLLECTION",
- label: "My Computer",
- icon: icons.computer,
- className: libraryClassName,
- isExpanded: true, // This node is expanded by default
- children: [
- {
- id: UUID(),
- nodeType: "LIB_NODE_GENERIC_EMPTY",
- label: "(No files to display)",
- icon: icons.misc,
- className: `${collectionClassName} text-gray-100/30`,
- isExpanded: false, // This node is expanded by default
- children: []
- }
- ]
- }
-
- const huggingfaceUserLibrary: LibraryTreeNode = {
- id: UUID(),
- nodeType: "LIB_NODE_HUGGINGFACE_USER_COLLECTION",
- label: "My HF Cloud",
- icon: icons.cloud,
- className: libraryClassName,
- isExpanded: true, // This node is expanded by default
- children: [
- {
- id: UUID(),
- nodeType: "LIB_NODE_GENERIC_EMPTY",
- label: "(No files to display)",
- icon: icons.misc,
- className: `${collectionClassName} text-gray-100/30`,
- isExpanded: false, // This node is expanded by default
- children: []
- }
- ]
- }
-
- const libraryTreeRoot = [
- localUserLibrary,
- huggingfaceUserLibrary,
- ]
-
- set({
- localUserLibraryTreeNodeId: localUserLibrary.id,
- huggingfaceUserLibraryTreeNodeId: huggingfaceUserLibrary.id,
- libraryTreeRoot,
- selectedNodeItem: undefined,
- selectedTreeNodeId: null,
- })
- },
-
- setLocalUserCollections: (collections: LocalUserCollection[]) => {
- const { localUserLibraryTreeNodeId, libraryTreeRoot } = get()
-
- console.log("setLocalUserCollections:", collections)
-
- set({
- libraryTreeRoot: libraryTreeRoot.map(node => {
- if (node.id !== localUserLibraryTreeNodeId) { return node }
-
- return {
- ...node,
-
- children: collections.map(c => ({
- id: UUID(),
- nodeType: "LIB_NODE_LOCAL_USER_FOLDER",
- data: c,
- label: c.name, // file directory name
- icon: getAppropriateIcon(c.name),
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
- isExpanded: false, // This node is expanded by default
- children: c.items.map(m => ({
- nodeType: "LIB_NODE_LOCAL_USER_FILE",
- id: m.id,
- data: m,
- label: <>{
- m.fileName.split(".").slice(0, -1)
- }{
- `.${m.fileName.split(".").pop()}`
- }>,
- icon: getAppropriateIcon(m.fileName, getAppropriateIcon(c.name)),
- className: `${itemClassName} ${
- getCollectionItemTextColor(m.fileName)
- }`, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
- }))
- }))
- }
- })
- })
- },
-
- setHuggingFaceUserCollections: (collections: HuggingFaceUserCollection[]) => {
- const { huggingfaceUserLibraryTreeNodeId, libraryTreeRoot } = get()
-
- set({
- libraryTreeRoot: libraryTreeRoot.map(node => {
- if (node.id !== huggingfaceUserLibraryTreeNodeId) { return node }
-
- return {
- ...node,
-
- children: collections.map(c => ({
- id: c.id,
- nodeType: "LIB_NODE_HUGGINGFACE_USER_DATASET",
- data: c,
- label: c.name, // file directory name
- icon: getAppropriateIcon(c.name),
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
- isExpanded: false, // This node is expanded by default
- children: c.items.map(m => ({
- nodeType: "LIB_NODE_HUGGINGFACE_USER_FILE",
- id: m.id,
- data: m,
- label: <>{
- m.fileName.split(".").slice(0, -1)
- }{
- `.${m.fileName.split(".").pop()}`
- }>,
- icon: getAppropriateIcon(m.fileName, getAppropriateIcon(c.name)),
- className: `${itemClassName} ${
- getCollectionItemTextColor(m.fileName)
- }`, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
- }))
- }))
- }
- })
- })
- },
-
- selectedNodeItem: undefined,
- selectEntity: (entity?: ClapEntity) => {
- if (entity) {
- console.log("TODO julian: change this code to search in the entity collections")
- const selectedTreeNode =
- get().libraryTreeRoot
- .find(node => node.data?.id === entity.id)
-
- // set({ selectedTreeNode })
- set({ selectedTreeNodeId: selectedTreeNode?.id || null })
- set({ selectedNodeItem: entity })
- } else {
- // set({ selectedTreeNode: undefined })
- set({ selectedTreeNodeId: null })
- set({ selectedNodeItem: undefined })
- }
- },
- // selectedTreeNode: undefined,
- selectedTreeNodeId: null,
- selectTreeNode: (treeNodeId?: string | null, nodeType?: LibraryNodeType, nodeItem?: LibraryNodeItem) => {
- set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
- set({ selectedNodeType: nodeType ? nodeType : undefined })
- set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
- }
-}))
-
-useFileLibrary.getState().init()
\ No newline at end of file
diff --git a/src/components/tree-browsers/stores/useProjectLibrary.ts b/src/components/tree-browsers/stores/useProjectLibrary.ts
deleted file mode 100644
index 3bf532ed..00000000
--- a/src/components/tree-browsers/stores/useProjectLibrary.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-'use client'
-
-import { create } from 'zustand'
-import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap'
-
-import { icons } from '@/components/icons'
-
-import { LibraryNodeItem, LibraryNodeType, LibraryTreeNode } from '../types'
-import {
- collectionClassName,
- itemClassName,
- libraryClassName,
-} from './treeNodeStyles'
-
-export const useProjectLibrary = create<{
- libraryTreeRoot: LibraryTreeNode[]
- init: () => void
- setProjectEntities: (entities: ClapEntity[]) => Promise
- selectedNodeItem?: LibraryNodeItem
- selectedNodeType?: LibraryNodeType
- selectEntity: (entity?: ClapEntity) => void
- selectTreeNode: (
- treeNodeId?: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => void
- selectedTreeNodeId: string | null
-}>((set, get) => ({
- libraryTreeRoot: [],
- init: () => {
- set({
- libraryTreeRoot: [],
- selectedNodeItem: undefined,
- selectedTreeNodeId: null,
- // selectedTreeNode: undefined,
- })
- },
-
- setProjectEntities: async (entities: ClapEntity[]) => {
- const { libraryTreeRoot } = get()
-
- const characters: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Characters',
- icon: icons.characters,
- className: libraryClassName,
- isExpanded: true, // This node is expanded by default
- children: [],
- }
-
- const locations: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Locations',
- icon: icons.location,
- className: libraryClassName,
- isExpanded: true, // This node is expanded by default
- children: [],
- }
-
- const misc: LibraryTreeNode = {
- id: UUID(),
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
- data: undefined,
- label: 'Misc',
- icon: icons.misc,
- className: libraryClassName,
- isExpanded: true, // This node is expanded by default
- children: [],
- }
-
- entities.forEach((entity) => {
- const node: LibraryTreeNode = {
- nodeType: 'LIB_NODE_PROJECT_ENTITY_GENERIC',
- id: entity.id,
- data: entity,
- label: entity.label,
- icon: icons.misc,
- className: collectionClassName,
- }
- if (entity.category === ClapSegmentCategory.CHARACTER) {
- node.icon = icons.character
- node.nodeType = 'LIB_NODE_PROJECT_ENTITY_CHARACTER'
- characters.children!.push(node)
- } else if (entity.category === ClapSegmentCategory.LOCATION) {
- node.icon = icons.location
- node.nodeType = 'LIB_NODE_PROJECT_ENTITY_LOCATION'
- locations.children!.push(node)
- } else {
- misc.children!.push(node)
- }
- })
-
- set({
- libraryTreeRoot: [
- characters,
- locations,
- misc,
- // displaying an empty collection isn't very useful,
- // so let's just clean them out
- ].filter((node) => node.children?.length),
- })
- },
-
- selectedNodeItem: undefined,
- selectEntity: (entity?: ClapEntity) => {
- if (entity) {
- console.log(
- 'TODO julian: change this code to search in the model collections'
- )
- const selectedTreeNode = get().libraryTreeRoot.find(
- (node) => node.data?.id === entity.id
- )
-
- // set({ selectedTreeNode })
- set({ selectedTreeNodeId: selectedTreeNode?.id || null })
- set({ selectedNodeItem: entity })
- } else {
- // set({ selectedTreeNode: undefined })
- set({ selectedTreeNodeId: null })
- set({ selectedNodeItem: undefined })
- }
- },
- // selectedTreeNode: undefined,
- selectedTreeNodeId: null,
- selectTreeNode: (
- treeNodeId?: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => {
- set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
- set({ selectedNodeType: nodeType ? nodeType : undefined })
- set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
- },
-}))
-
-useProjectLibrary.getState().init()
diff --git a/src/components/tree-browsers/stores/useReplicateCollections.ts b/src/components/tree-browsers/stores/useReplicateCollections.ts
deleted file mode 100644
index c07e25a8..00000000
--- a/src/components/tree-browsers/stores/useReplicateCollections.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client'
-
-import { useEffect, useState, useTransition } from 'react'
-import { ReplicateCollection } from '../types'
-
-export function useReplicateCollections(): ReplicateCollection[] {
- const [_pending, startTransition] = useTransition()
- const [collections, setCollections] = useState([])
- // const [models, setModels] = useState([])
-
- useEffect(() => {
- startTransition(async () => {
- // TODO @Julian: this was something we did in the ligacy
- // Clapper, but I'm not sure we want to support Replicate
- // again just yet, we probably require other arch changes
- // const collections = await listCollections()
- const collections: ReplicateCollection[] = []
- setCollections(collections)
- // setModels(models)
- })
- }, [])
-
- return collections
-}
diff --git a/src/components/tree-browsers/style/treeNodeStyles.ts b/src/components/tree-browsers/style/treeNodeStyles.ts
new file mode 100644
index 00000000..d1af4c70
--- /dev/null
+++ b/src/components/tree-browsers/style/treeNodeStyles.ts
@@ -0,0 +1,10 @@
+// TODO: this isn't the best place for this as this is style,
+// and we are in a state manager
+export const libraryClassName =
+ 'text-sm font-semibold text-white/80 hover:text-white/90'
+
+export const collectionClassName =
+ 'text-sm font-normal text-white/70 hover:text-white/90'
+
+export const itemClassName =
+ 'text-xs font-light text-white/60 hover:text-white/90'
diff --git a/src/components/tree-browsers/types.ts b/src/components/tree-browsers/types.ts
index b4e29639..efeba2c3 100644
--- a/src/components/tree-browsers/types.ts
+++ b/src/components/tree-browsers/types.ts
@@ -4,67 +4,72 @@ import { ScreenplaySequence } from '@aitube/broadway'
import { ClapEntity, ClapSegment, ClapWorkflow } from '@aitube/clap'
import { TreeNodeType } from '../core/tree/types'
-// not sure if we should also sort them into data type categories,
-// as vendors like to be on multiple kind of models
-
-export type LibraryNodeHuggingFaceType =
- | 'LIB_NODE_HUGGINGFACE_USER_COLLECTION'
- | 'LIB_NODE_HUGGINGFACE_USER_DATASET'
-
-export type LibraryNodeLocalFileType =
- | 'LIB_NODE_LOCAL_USER_FILE'
- | 'LIB_NODE_LOCAL_USER_FOLDER'
-
-export type LibraryNodeRemoteFileType =
- | 'LIB_NODE_HUGGINGFACE_USER_FOLDER'
- | 'LIB_NODE_HUGGINGFACE_USER_FILE'
-
-export type LibraryNodeFileType =
- | LibraryNodeLocalFileType
- | LibraryNodeRemoteFileType
-
-export type LibraryNodeWorkflowType = 'LIB_NODE_WORKFLOWS' | 'LIB_NODE_WORKFLOW'
-
-export type LibraryNodeProjectType =
- | 'LIB_NODE_PROJECT_COLLECTION'
- | 'LIB_NODE_PROJECT_ARCHIVE'
- | 'LIB_NODE_PROJECT_ASSET' // image, sound file..
- | 'LIB_NODE_PROJECT_ENTITY_GENERIC'
- | 'LIB_NODE_PROJECT_ENTITY_CHARACTER'
- | 'LIB_NODE_PROJECT_ENTITY_LOCATION'
-
-export type LibraryNodeGenericType =
- | 'LIB_NODE_GENERIC_COLLECTION'
- | 'LIB_NODE_GENERIC_MODEL'
- | 'LIB_NODE_GENERIC_ITEM'
- | 'LIB_NODE_GENERIC_EMPTY'
-
-/**
- * we could use "LIB_NODE_GENERIC_COLLECTION",
- * but I think it can also be useful to keep specific types,
- * that way we can show a custom collection UI panel on the right of the explorer
- */
-export type LibraryNodeType =
- | 'LIB_NODE_LOCAL_USER_COLLECTION'
- | LibraryNodeLocalFileType
- | LibraryNodeHuggingFaceType
- | LibraryNodeRemoteFileType
- | LibraryNodeWorkflowType
- | LibraryNodeProjectType
- | 'LIB_NODE_TEAM_COLLECTION'
- | 'LIB_NODE_TEAM_MODEL'
- | 'LIB_NODE_COMMUNITY_COLLECTION'
- | 'LIB_NODE_COMMUNITY_MODEL'
- | 'LIB_NODE_HUGGINGFACE_COLLECTION'
- | 'LIB_NODE_HUGGINGFACE_MODEL'
- | 'LIB_NODE_REPLICATE_COLLECTION'
- | 'LIB_NODE_REPLICATE_MODEL'
- | 'LIB_NODE_CIVITAI_COLLECTION'
- | 'LIB_NODE_CIVITAI_MODEL'
- | LibraryNodeGenericType
+export type DefaultTreeNodeList = 'DEFAULT_TREE_NODE_LIST'
+export type DefaultTreeNodeItem = 'DEFAULT_TREE_NODE_ITEM'
+export type DefaultTreeNodeEmpty = 'DEFAULT_TREE_NODE_EMPTY'
+
+export type DefaultTreeNode =
+ | DefaultTreeNodeList
+ | DefaultTreeNodeItem
+ | DefaultTreeNodeEmpty
+
+// ------------------------------------------
+export type CommunityTreeNodeDataset = 'COMMUNITY_TREE_NODE_LIST_DATASET'
+export type CommunityTreeNodeList = 'COMMUNITY_TREE_NODE_LIST_FOLDER'
+export type CommunityTreeNodeItem = 'COMMUNITY_TREE_NODE_ITEM_FILE'
+
+export type CommunityTreeNode =
+ | CommunityTreeNodeDataset
+ | CommunityTreeNodeList
+ | CommunityTreeNodeItem
+
+// ------------------------------------------
+
+export type DeviceTreeNodeList = 'DEVICE_TREE_NODE_LIST_FOLDER'
+export type DeviceTreeNodeItem = 'DEVICE_TREE_NODE_ITEM_FILE'
+
+export type DeviceTreeNode = DeviceTreeNodeList | DeviceTreeNodeItem
+
+// ------------------------------------------
+
+export type FlowTreeNodeList = 'FLOW_TREE_NODE_LIST_WORKFLOWS'
+export type FlowTreeNodeItem = 'FLOW_TREE_NODE_ITEM_WORKFLOW'
+
+export type FlowTreeNode = FlowTreeNodeList | FlowTreeNodeItem
+
+// ------------------------------------------
+
+export type EntityTreeNodeList = 'ENTITY_TREE_NODE_LIST_ENTITIES'
+export type EntityTreeNodeItem = 'ENTITY_TREE_NODE_ITEM_ENTITY'
+
+export type EntityTreeNode = EntityTreeNodeList | EntityTreeNodeItem
+
+// ------------------------------------------
+
+// some specialized types
+
+// TODO
+
+// ------------------------------------------
+
+// Root elements
+export type TreeRootProject = 'TREE_ROOT_PROJECT'
+export type TreeRootDevice = 'TREE_ROOT_DEVICE'
+export type TreeRootTeam = 'TREE_ROOT_TEAM'
+export type TreeRootCommunity = 'TREE_ROOT_COMMUNITY'
+export type TreeRootBuiltin = 'TREE_ROOT_BUILTIN'
+
+export type TreeRoot =
+ | TreeRootProject
+ | TreeRootDevice
+ | TreeRootTeam
+ | TreeRootCommunity
+ | TreeRootBuiltin
+
+// ------------------------------------------
// can be a file or folder
-export type LocalUserItem = {
+export type DeviceFileOrFolder = {
id: string // can be the path for now
path: string // path to the file content
fileName: string
@@ -73,19 +78,19 @@ export type LocalUserItem = {
extension: string
mimetype: string
isDirectory: boolean
- items: LocalUserItem[]
+ items: DeviceFileOrFolder[]
}
-export type LocalUserCollection = {
+export type DeviceCollection = {
id: string
name: string
description: string
path: string // path to the root directory (eg "~/Clapper")
- items: LocalUserItem[] // files or folders
+ items: DeviceFileOrFolder[] // files or folders
}
// can be a file or folder
-export type HuggingFaceUserItem = {
+export type CommunityFileOrFolder = {
id: string // can be the path for now (so not a uuid)
assetUrl: string // full URL to download the content (eg. to use with wget)
datasetName: string
@@ -99,18 +104,25 @@ export type HuggingFaceUserItem = {
mimeType: string
isDirectory: boolean
isPrivate: boolean
- items: HuggingFaceUserItem[]
+ items: CommunityFileOrFolder[]
}
-export type HuggingFaceUserCollection = {
- id: string // huggingface ud (not a uuid)
+export type CommunityEntityCollection = {
+ id: string // huggingface id (not a uuid)
name: string
description: string
userName: string
datasetName: string
repository: string
url: string
- items: HuggingFaceUserItem[] // files or folders
+ items: ClapEntity[]
+}
+
+export type ProjectEntityCollection = {
+ id: string
+ name: string
+ description: string
+ items: ClapEntity[]
}
export type ReplicateModel = {
@@ -158,24 +170,38 @@ export type WorkflowCollection = {
}
// TODO unify this a bit, at least in the naming scheme
-export type LibraryNodeFileItem = LocalUserItem | HuggingFaceUserItem
+export type LibraryNodeFileItem = DeviceFileOrFolder | CommunityFileOrFolder
+
+// ------------------------------------------
+
+export type LibraryNodeType =
+ | DefaultTreeNode
+ | CommunityTreeNode
+ | DeviceTreeNode
+ | FlowTreeNode
+ | EntityTreeNode
+ | TreeRoot
// TODO unify this a bit, at least in the naming scheme
-export type LibraryNodeItem =
+export type TreeNodeItem =
| ClapEntity
- | ReplicateCollection
- | ReplicateModel
- | CivitaiCollection
- | CivitaiModel
- | LocalUserCollection
- | LocalUserItem
- | HuggingFaceUserCollection
- | HuggingFaceUserItem
- | ScreenplaySequence
| ClapSegment
- | WorkflowCollection
+ | ScreenplaySequence
| ClapWorkflow
+ | WorkflowCollection
+
+ // TODO: if we keep them, then rename those to things like
+ // ReplicateWorkflowCollection,
+ // CivitaiWorkflowCollection etc
+ // | ReplicateCollection
+ // | ReplicateModel
+ // | CivitaiCollection
+ // | CivitaiModel
+ | DeviceCollection
+ | DeviceFileOrFolder
+ | CommunityEntityCollection
+ | CommunityFileOrFolder
// a model library is a collection of models
// this collection can itself include sub-models
-export type LibraryTreeNode = TreeNodeType
+export type LibraryTreeNode = TreeNodeType
diff --git a/src/components/tree-browsers/utils/isSomething.ts b/src/components/tree-browsers/utils/isSomething.ts
index 6dc441cc..90e2c2e9 100644
--- a/src/components/tree-browsers/utils/isSomething.ts
+++ b/src/components/tree-browsers/utils/isSomething.ts
@@ -6,63 +6,51 @@
import { ClapEntity } from '@aitube/clap'
import {
- HuggingFaceUserCollection,
- HuggingFaceUserItem,
- LibraryNodeItem,
+ CommunityEntityCollection,
+ CommunityFileOrFolder,
+ TreeNodeItem,
LibraryNodeType,
- LocalUserCollection,
- LocalUserItem,
- ReplicateCollection,
+ DeviceCollection,
+ DeviceFileOrFolder,
} from '../types'
-export const isLocalUserCollection = (
+export const isFSCollection = (
nodeType: LibraryNodeType,
- data: LibraryNodeItem
-): data is LocalUserCollection => {
- return nodeType === 'LIB_NODE_LOCAL_USER_COLLECTION'
+ data: TreeNodeItem
+): data is DeviceCollection => {
+ return nodeType === 'TREE_ROOT_DEVICE'
}
-export const isLocalUserItem = (
+export const isFSFileOrFolder = (
nodeType: LibraryNodeType,
- data: LibraryNodeItem
-): data is LocalUserItem => {
+ data: TreeNodeItem
+): data is DeviceFileOrFolder => {
return (
- nodeType === 'LIB_NODE_LOCAL_USER_FILE' ||
- nodeType === 'LIB_NODE_LOCAL_USER_FOLDER'
+ nodeType === 'DEVICE_TREE_NODE_LIST_FOLDER' ||
+ nodeType === 'DEVICE_TREE_NODE_ITEM_FILE'
)
}
-export const isHuggingFaceUserCollection = (
+export const isCommunityEntityCollection = (
nodeType: LibraryNodeType,
- data: LibraryNodeItem
-): data is HuggingFaceUserCollection => {
- return nodeType === 'LIB_NODE_HUGGINGFACE_USER_COLLECTION'
+ data: TreeNodeItem
+): data is CommunityEntityCollection => {
+ return nodeType === 'COMMUNITY_TREE_NODE_LIST_DATASET'
}
-export const isHuggingFaceUserItem = (
+export const isCommunityFileOrFolder = (
nodeType: LibraryNodeType,
- data: LibraryNodeItem
-): data is HuggingFaceUserItem => {
+ data: TreeNodeItem
+): data is CommunityFileOrFolder => {
return (
- nodeType === 'LIB_NODE_HUGGINGFACE_USER_FILE' ||
- nodeType === 'LIB_NODE_HUGGINGFACE_USER_FOLDER'
+ nodeType === 'COMMUNITY_TREE_NODE_LIST_FOLDER' ||
+ nodeType === 'COMMUNITY_TREE_NODE_ITEM_FILE'
)
}
-export const isReplicateCollection = (
- nodeType: LibraryNodeType,
- data: LibraryNodeItem
-): data is ReplicateCollection => {
- return nodeType === 'LIB_NODE_REPLICATE_COLLECTION'
-}
-
export const isClapEntity = (
nodeType: LibraryNodeType,
- data: LibraryNodeItem
+ data: TreeNodeItem
): data is ClapEntity => {
- return (
- nodeType === 'LIB_NODE_REPLICATE_MODEL' ||
- nodeType === 'LIB_NODE_HUGGINGFACE_MODEL' ||
- nodeType === 'LIB_NODE_GENERIC_MODEL'
- )
+ return nodeType === 'ENTITY_TREE_NODE_ITEM_ENTITY'
}
diff --git a/src/components/tree-browsers/workflow-tree-browser/index.tsx b/src/components/tree-browsers/workflow-tree-browser/index.tsx
deleted file mode 100644
index 578a8c45..00000000
--- a/src/components/tree-browsers/workflow-tree-browser/index.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-'use client'
-
-import { useEffect } from 'react'
-
-import { cn } from '@/lib/utils'
-import { useEntityLibrary } from '../stores/useEntityLibrary'
-import { LibraryNodeItem, LibraryNodeType } from '../types'
-import { Tree } from '@/components/core/tree'
-
-import { isClapEntity, isReplicateCollection } from '../utils/isSomething'
-
-export function WorkflowTreeBrowser() {
- const libraryTreeRoot = useEntityLibrary((s) => s.libraryTreeRoot)
- const selectTreeNode = useEntityLibrary((s) => s.selectTreeNode)
- const selectedTreeNodeId = useEntityLibrary((s) => s.selectedTreeNodeId)
-
- /**
- * handle click on tree node
- * yes, this is where the magic happens!
- *
- * @param id
- * @param nodeType
- * @param node
- * @returns
- */
- const handleOnChange = async (
- id: string | null,
- nodeType?: LibraryNodeType,
- nodeItem?: LibraryNodeItem
- ) => {
- console.log(`calling selectTreeNodeById(id)`)
- selectTreeNode(id, nodeType, nodeItem)
-
- if (!nodeType || !nodeItem) {
- console.log('tree-browser: clicked on an undefined node')
- return
- }
-
- if (isReplicateCollection(nodeType, nodeItem)) {
- // ReplicateCollection
- } else if (isClapEntity(nodeType, nodeItem)) {
- // ClapEntity
- } else {
- console.log(
- `tree-browser: no action attached to ${nodeType}, so skipping`
- )
- return
- }
- console.log(`tree-browser: clicked on a ${nodeType}`, nodeItem)
- }
-
- return (
-
-
- value={selectedTreeNodeId}
- onChange={handleOnChange}
- className="not-prose h-full w-full px-2 pt-8"
- label="Model Library"
- >
- {libraryTreeRoot.map((node) => (
-
- ))}
-
-
- )
-}
diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx
index 07761414..2a40ef85 100644
--- a/src/components/ui/menubar.tsx
+++ b/src/components/ui/menubar.tsx
@@ -23,7 +23,7 @@ const Menubar = React.forwardRef<
(({ className, ...props }, ref) => (
))
@@ -207,7 +210,7 @@ const MenubarShortcut = ({
return (
((set, get) => ({
if (!textModel) {
return
}
- // we need to update the model
- textModel?.setValue(draft)
+
+ try {
+ // we need to update the model
+ textModel?.setValue(draft)
+ } catch (err) {
+ // to catch the "Error: Model is disposed!"
+ // this can happen if the timing isn't right,
+ // the model might already (or still) be disposed of
+ return
+ }
// and highlight the text again
- highlightElements()
+ try {
+ highlightElements()
+ } catch (err) {}
},
publishDraftToTimeline: async (): Promise => {
const { draft } = get()