jbilcke-hf HF staff commited on
Commit
3420261
β€’
1 Parent(s): c1021fe

add minimalist tree system

Browse files
Files changed (34) hide show
  1. src/components/{tree-browsers/project-tree-browser β†’ editors/EntityEditor/EntityTree}/index.tsx +23 -20
  2. src/components/{tree-browsers/stores/useProjectLibrary.ts β†’ editors/EntityEditor/EntityTree/useEntityTree.ts} +103 -36
  3. src/components/editors/EntityEditor/EntityViewer/EntityList.tsx +65 -0
  4. src/components/editors/EntityEditor/EntityViewer/index.tsx +192 -0
  5. src/components/editors/EntityEditor/index.tsx +12 -238
  6. src/components/{tree-browsers/workflow-tree-browser β†’ editors/WorkflowEditor/WorkflowTree}/index.tsx +23 -26
  7. src/components/{tree-browsers/stores/useWorkflowLibrary.ts β†’ editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts} +29 -21
  8. src/components/editors/WorkflowEditor/{viewer β†’ WorkflowViewer/ReactFlowCanvas}/NodeView.tsx +1 -1
  9. src/components/editors/WorkflowEditor/{clapWorkflowToReactWorkflow.ts β†’ WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts} +1 -1
  10. src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/comfyui/types.ts +0 -0
  11. src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/falai/types.ts +0 -0
  12. src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/glif/glifToReactWorkflow.ts +0 -0
  13. src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/glif/types.ts +0 -0
  14. src/components/editors/WorkflowEditor/{viewer/WorkflowView.tsx β†’ WorkflowViewer/ReactFlowCanvas/index.tsx} +24 -5
  15. src/components/editors/WorkflowEditor/{samples β†’ WorkflowViewer/ReactFlowCanvas/samples}/glif.ts +1 -1
  16. src/components/editors/WorkflowEditor/{types.ts β†’ WorkflowViewer/ReactFlowCanvas/types.ts} +0 -0
  17. src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx +31 -0
  18. src/components/editors/WorkflowEditor/index.tsx +12 -32
  19. src/components/editors/WorkflowEditor/viewer/README.md +0 -1
  20. src/components/forms/index.ts +9 -0
  21. src/components/toolbars/top-bar/index.tsx +18 -11
  22. src/components/toolbars/top-menu/index.tsx +1 -1
  23. src/components/tree-browsers/model-tree-browser/index.tsx +0 -88
  24. src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx +0 -17
  25. src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx +0 -17
  26. src/components/tree-browsers/stores/useCivitaiCollections.ts +0 -24
  27. src/components/tree-browsers/stores/useEntityLibrary.ts +0 -336
  28. src/components/tree-browsers/stores/useFileLibrary.txt +0 -204
  29. src/components/tree-browsers/stores/useReplicateCollections.ts +0 -24
  30. src/components/tree-browsers/{stores β†’ style}/treeNodeStyles.ts +5 -3
  31. src/components/tree-browsers/types.ts +106 -80
  32. src/components/tree-browsers/utils/isSomething.ts +25 -37
  33. src/components/ui/menubar.tsx +13 -10
  34. src/services/editors/script-editor/useScriptEditor.ts +13 -3
src/components/{tree-browsers/project-tree-browser β†’ editors/EntityEditor/EntityTree}/index.tsx RENAMED
@@ -1,15 +1,20 @@
1
  'use client'
2
 
3
  import { cn } from '@/lib/utils'
4
- import { isClapEntity } from '../utils/isSomething'
5
- import { useProjectLibrary } from '../stores/useProjectLibrary'
6
- import { LibraryNodeItem, LibraryNodeType } from '../types'
7
  import { Tree } from '@/components/core/tree'
8
 
9
- export function ProjectTreeBrowser() {
10
- const libraryTreeRoot = useProjectLibrary((s) => s.libraryTreeRoot)
11
- const selectTreeNode = useProjectLibrary((s) => s.selectTreeNode)
12
- const selectedTreeNodeId = useProjectLibrary((s) => s.selectedTreeNodeId)
 
 
 
 
 
 
13
 
14
  /**
15
  * handle click on tree node
@@ -23,7 +28,7 @@ export function ProjectTreeBrowser() {
23
  const handleOnChange = async (
24
  id: string | null,
25
  nodeType?: LibraryNodeType,
26
- nodeItem?: LibraryNodeItem
27
  ) => {
28
  console.log(`calling selectTreeNodeById(id)`)
29
  selectTreeNode(id, nodeType, nodeItem)
@@ -44,17 +49,15 @@ export function ProjectTreeBrowser() {
44
  }
45
 
46
  return (
47
- <div className={cn()}>
48
- <Tree.Root<LibraryNodeType, LibraryNodeItem>
49
- value={selectedTreeNodeId}
50
- onChange={handleOnChange}
51
- className="not-prose h-full w-full px-2 pt-8"
52
- label="Project Library"
53
- >
54
- {libraryTreeRoot.map((node) => (
55
- <Tree.Node node={node} key={node.id} />
56
- ))}
57
- </Tree.Root>
58
- </div>
59
  )
60
  }
 
1
  'use client'
2
 
3
  import { cn } from '@/lib/utils'
4
+ import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
5
+ import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
 
6
  import { Tree } from '@/components/core/tree'
7
 
8
+ import { useEntityTree } from './useEntityTree'
9
+
10
+ export function EntityTree({
11
+ className = '',
12
+ }: {
13
+ className?: string
14
+ } = {}) {
15
+ const libraryTreeRoot = useEntityTree((s) => s.libraryTreeRoot)
16
+ const selectTreeNode = useEntityTree((s) => s.selectTreeNode)
17
+ const selectedTreeNodeId = useEntityTree((s) => s.selectedTreeNodeId)
18
 
19
  /**
20
  * handle click on tree node
 
28
  const handleOnChange = async (
29
  id: string | null,
30
  nodeType?: LibraryNodeType,
31
+ nodeItem?: TreeNodeItem
32
  ) => {
33
  console.log(`calling selectTreeNodeById(id)`)
34
  selectTreeNode(id, nodeType, nodeItem)
 
49
  }
50
 
51
  return (
52
+ <Tree.Root<LibraryNodeType, TreeNodeItem>
53
+ value={selectedTreeNodeId}
54
+ onChange={handleOnChange}
55
+ className={cn(`not-prose h-full w-full px-2 pt-2`, className)}
56
+ label="Entities"
57
+ >
58
+ {libraryTreeRoot.map((node) => (
59
+ <Tree.Node node={node} key={node.id} />
60
+ ))}
61
+ </Tree.Root>
 
 
62
  )
63
  }
src/components/{tree-browsers/stores/useProjectLibrary.ts β†’ editors/EntityEditor/EntityTree/useEntityTree.ts} RENAMED
@@ -1,51 +1,123 @@
1
  'use client'
2
 
3
  import { create } from 'zustand'
4
- import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap'
5
-
 
 
 
 
6
  import { icons } from '@/components/icons'
7
-
8
- import { LibraryNodeItem, LibraryNodeType, LibraryTreeNode } from '../types'
9
  import {
10
  collectionClassName,
11
- itemClassName,
12
  libraryClassName,
13
- } from './treeNodeStyles'
 
 
 
 
 
 
 
 
 
 
14
 
15
- export const useProjectLibrary = create<{
16
  libraryTreeRoot: LibraryTreeNode[]
17
  init: () => void
18
- setProjectEntities: (entities: ClapEntity[]) => Promise<void>
19
- selectedNodeItem?: LibraryNodeItem
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  selectedNodeType?: LibraryNodeType
21
- selectEntity: (entity?: ClapEntity) => void
22
  selectTreeNode: (
23
  treeNodeId?: string | null,
24
  nodeType?: LibraryNodeType,
25
- nodeItem?: LibraryNodeItem
26
  ) => void
27
  selectedTreeNodeId: string | null
28
  }>((set, get) => ({
 
 
 
 
 
 
 
 
29
  libraryTreeRoot: [],
30
  init: () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  set({
32
- libraryTreeRoot: [],
 
 
33
  selectedNodeItem: undefined,
34
  selectedTreeNodeId: null,
35
- // selectedTreeNode: undefined,
36
  })
37
  },
38
 
 
39
  setProjectEntities: async (entities: ClapEntity[]) => {
40
- const { libraryTreeRoot } = get()
41
-
42
  const characters: LibraryTreeNode = {
43
  id: UUID(),
44
  nodeType: 'LIB_NODE_GENERIC_COLLECTION',
45
  data: undefined,
46
  label: 'Characters',
47
  icon: icons.characters,
48
- className: libraryClassName,
49
  isExpanded: true, // This node is expanded by default
50
  children: [],
51
  }
@@ -56,8 +128,8 @@ export const useProjectLibrary = create<{
56
  data: undefined,
57
  label: 'Locations',
58
  icon: icons.location,
59
- className: libraryClassName,
60
- isExpanded: true, // This node is expanded by default
61
  children: [],
62
  }
63
 
@@ -67,49 +139,43 @@ export const useProjectLibrary = create<{
67
  data: undefined,
68
  label: 'Misc',
69
  icon: icons.misc,
70
- className: libraryClassName,
71
- isExpanded: true, // This node is expanded by default
72
  children: [],
73
  }
74
 
75
  entities.forEach((entity) => {
76
  const node: LibraryTreeNode = {
77
- nodeType: 'LIB_NODE_PROJECT_ENTITY_GENERIC',
78
  id: entity.id,
79
  data: entity,
80
  label: entity.label,
81
  icon: icons.misc,
82
- className: collectionClassName,
83
  }
84
  if (entity.category === ClapSegmentCategory.CHARACTER) {
85
  node.icon = icons.character
86
- node.nodeType = 'LIB_NODE_PROJECT_ENTITY_CHARACTER'
87
  characters.children!.push(node)
88
  } else if (entity.category === ClapSegmentCategory.LOCATION) {
89
  node.icon = icons.location
90
- node.nodeType = 'LIB_NODE_PROJECT_ENTITY_LOCATION'
91
  locations.children!.push(node)
92
  } else {
93
  misc.children!.push(node)
94
  }
95
  })
 
96
 
97
- set({
98
- libraryTreeRoot: [
99
- characters,
100
- locations,
101
- misc,
102
- // displaying an empty collection isn't very useful,
103
- // so let's just clean them out
104
- ].filter((node) => node.children?.length),
105
- })
106
  },
 
107
 
108
  selectedNodeItem: undefined,
109
  selectEntity: (entity?: ClapEntity) => {
110
  if (entity) {
111
  console.log(
112
- 'TODO julian: change this code to search in the model collections'
113
  )
114
  const selectedTreeNode = get().libraryTreeRoot.find(
115
  (node) => node.data?.id === entity.id
@@ -124,12 +190,13 @@ export const useProjectLibrary = create<{
124
  set({ selectedNodeItem: undefined })
125
  }
126
  },
 
127
  // selectedTreeNode: undefined,
128
  selectedTreeNodeId: null,
129
  selectTreeNode: (
130
  treeNodeId?: string | null,
131
  nodeType?: LibraryNodeType,
132
- nodeItem?: LibraryNodeItem
133
  ) => {
134
  set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
135
  set({ selectedNodeType: nodeType ? nodeType : undefined })
@@ -137,4 +204,4 @@ export const useProjectLibrary = create<{
137
  },
138
  }))
139
 
140
- useProjectLibrary.getState().init()
 
1
  'use client'
2
 
3
  import { create } from 'zustand'
4
+ import { ClapEntity, UUID } from '@aitube/clap'
5
+ import {
6
+ LibraryTreeNode,
7
+ TreeNodeItem,
8
+ LibraryNodeType,
9
+ } from '@/components/tree-browsers/types'
10
  import { icons } from '@/components/icons'
11
+ import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
 
12
  import {
13
  collectionClassName,
 
14
  libraryClassName,
15
+ } from '@/components/tree-browsers/style/treeNodeStyles'
16
+
17
+ export const useEntityTree = create<{
18
+ // project entities stored in the .clap
19
+ projectLibraryTreeNodeId: string
20
+
21
+ // in the future, we are going to put
22
+ // <placeholder>
23
+
24
+ // entities stored on the public database (Hugging Face datasets, tagged)
25
+ communityLibraryTreeNodeId: string
26
 
 
27
  libraryTreeRoot: LibraryTreeNode[]
28
  init: () => void
29
+
30
+ /**
31
+ * Load entity collections (characters, locations..) from the clap project into the tree
32
+ *
33
+ * @param collections
34
+ * @returns
35
+ */
36
+ // setProjectLibrary: (collections: ProjectEntityCollection[]) => void
37
+
38
+ /**
39
+ * Load entity collections (characters, locations..) from the Clapper community into the tree
40
+ *
41
+ * @param collections
42
+ * @returns
43
+ */
44
+ // setCommunityLibrary: (collections: CommunityEntityCollection[]) => void
45
+
46
+ // we support those all selection modes for convenience - please keep them!
47
+ selectedNodeItem?: TreeNodeItem
48
  selectedNodeType?: LibraryNodeType
 
49
  selectTreeNode: (
50
  treeNodeId?: string | null,
51
  nodeType?: LibraryNodeType,
52
+ nodeItem?: TreeNodeItem
53
  ) => void
54
  selectedTreeNodeId: string | null
55
  }>((set, get) => ({
56
+ // project entities stored in the .clap
57
+ projectLibraryTreeNodeId: '',
58
+
59
+ // in the future, we are going to put
60
+ // <placeholder>
61
+
62
+ // entities stored on the public database (Hugging Face datasets, tagged)
63
+ communityLibraryTreeNodeId: '',
64
  libraryTreeRoot: [],
65
  init: () => {
66
+ const projectLibrary: LibraryTreeNode = {
67
+ id: UUID(),
68
+ nodeType: 'TREE_ROOT_PROJECT',
69
+ label: 'Project entities',
70
+ icon: icons.project,
71
+ className: libraryClassName,
72
+ isExpanded: true,
73
+ children: [
74
+ {
75
+ id: UUID(),
76
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
77
+ label: 'Empty',
78
+ icon: icons.project,
79
+ className: collectionClassName,
80
+ },
81
+ ],
82
+ }
83
+
84
+ const communityLibrary: LibraryTreeNode = {
85
+ id: UUID(),
86
+ nodeType: 'TREE_ROOT_COMMUNITY',
87
+ label: 'Community entities',
88
+ icon: icons.community,
89
+ className: libraryClassName,
90
+ children: [
91
+ {
92
+ id: UUID(),
93
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
94
+ label: 'Empty',
95
+ icon: icons.community,
96
+ className: collectionClassName,
97
+ },
98
+ ],
99
+ }
100
+
101
+ const libraryTreeRoot = [projectLibrary, communityLibrary]
102
+
103
  set({
104
+ projectLibraryTreeNodeId: projectLibrary.id,
105
+ communityLibraryTreeNodeId: communityLibrary.id,
106
+ libraryTreeRoot,
107
  selectedNodeItem: undefined,
108
  selectedTreeNodeId: null,
 
109
  })
110
  },
111
 
112
+ /*
113
  setProjectEntities: async (entities: ClapEntity[]) => {
 
 
114
  const characters: LibraryTreeNode = {
115
  id: UUID(),
116
  nodeType: 'LIB_NODE_GENERIC_COLLECTION',
117
  data: undefined,
118
  label: 'Characters',
119
  icon: icons.characters,
120
+ className: collectionClassName,
121
  isExpanded: true, // This node is expanded by default
122
  children: [],
123
  }
 
128
  data: undefined,
129
  label: 'Locations',
130
  icon: icons.location,
131
+ className: collectionClassName,
132
+ isExpanded: false, // This node is expanded by default
133
  children: [],
134
  }
135
 
 
139
  data: undefined,
140
  label: 'Misc',
141
  icon: icons.misc,
142
+ className: collectionClassName,
143
+ isExpanded: false, // This node is expanded by default
144
  children: [],
145
  }
146
 
147
  entities.forEach((entity) => {
148
  const node: LibraryTreeNode = {
149
+ nodeType: TreeNodeEntityItem,
150
  id: entity.id,
151
  data: entity,
152
  label: entity.label,
153
  icon: icons.misc,
154
+ className: itemClassName,
155
  }
156
  if (entity.category === ClapSegmentCategory.CHARACTER) {
157
  node.icon = icons.character
 
158
  characters.children!.push(node)
159
  } else if (entity.category === ClapSegmentCategory.LOCATION) {
160
  node.icon = icons.location
 
161
  locations.children!.push(node)
162
  } else {
163
  misc.children!.push(node)
164
  }
165
  })
166
+ },
167
 
168
+ setCommunityCollections: (collections: CommunityEntityCollection[]) => {
169
+ // TODO: implement this
170
+
 
 
 
 
 
 
171
  },
172
+ */
173
 
174
  selectedNodeItem: undefined,
175
  selectEntity: (entity?: ClapEntity) => {
176
  if (entity) {
177
  console.log(
178
+ 'TODO julian: change this code to search in the entity collections'
179
  )
180
  const selectedTreeNode = get().libraryTreeRoot.find(
181
  (node) => node.data?.id === entity.id
 
190
  set({ selectedNodeItem: undefined })
191
  }
192
  },
193
+
194
  // selectedTreeNode: undefined,
195
  selectedTreeNodeId: null,
196
  selectTreeNode: (
197
  treeNodeId?: string | null,
198
  nodeType?: LibraryNodeType,
199
+ nodeItem?: TreeNodeItem
200
  ) => {
201
  set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
202
  set({ selectedNodeType: nodeType ? nodeType : undefined })
 
204
  },
205
  }))
206
 
207
+ useEntityTree.getState().init()
src/components/editors/EntityEditor/EntityViewer/EntityList.tsx ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ClapEntity, ClapSegmentCategory, newEntity } from '@aitube/clap'
2
+ import { useTimeline } from '@aitube/timeline'
3
+
4
+ import { Button } from '@/components/ui/button'
5
+ import { useEntityEditor, useIO } from '@/services'
6
+
7
+ export function EntityList({
8
+ onSelectEntity,
9
+ }: {
10
+ onSelectEntity: (entityId: string) => void
11
+ }) {
12
+ const entities = useTimeline((s) => s.entities)
13
+ const setCurrent = useEntityEditor((s) => s.setCurrent)
14
+ const addEntity = useEntityEditor((s) => s.addEntity)
15
+ const removeEntity = useEntityEditor((s) => s.removeEntity)
16
+
17
+ const handleAddEntity = () => {
18
+ const entity: ClapEntity = newEntity({
19
+ id: Date.now().toString(),
20
+ label: 'NEW_ENTITY',
21
+ category: ClapSegmentCategory.CHARACTER,
22
+ description: '',
23
+ appearance: '',
24
+ }) // ignoring some fields for now
25
+ addEntity(entity)
26
+ }
27
+
28
+ return (
29
+ <div className="pt-4">
30
+ <div className="mb-2">
31
+ <h1 className="mb-4 inline px-4 text-xl font-bold">Entities</h1>
32
+ <Button
33
+ onClick={handleAddEntity}
34
+ className="absolute right-2 top-2"
35
+ variant="secondary"
36
+ >
37
+ New +
38
+ </Button>
39
+ </div>
40
+ <ul>
41
+ {entities.map((entity: ClapEntity) => (
42
+ <li key={entity.id} className={`flex px-2 py-1`}>
43
+ <Button
44
+ onClick={() => {
45
+ setCurrent(entity)
46
+ onSelectEntity(entity.id)
47
+ }}
48
+ variant="ghost"
49
+ >
50
+ {entity.label} ({entity.category})
51
+ </Button>
52
+ <Button
53
+ onClick={() => removeEntity(entity.id)}
54
+ className={`ml-2 ml-auto`}
55
+ variant="destructive"
56
+ size="sm"
57
+ >
58
+ Remove
59
+ </Button>
60
+ </li>
61
+ ))}
62
+ </ul>
63
+ </div>
64
+ )
65
+ }
src/components/editors/EntityEditor/EntityViewer/index.tsx ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react'
2
+ import { ClapEntity, ClapSegmentCategory } from '@aitube/clap'
3
+ import { useTimeline } from '@aitube/timeline'
4
+
5
+ import { FormFile } from '@/components/forms/FormFile'
6
+ import { FormInput } from '@/components/forms/FormInput'
7
+ import { FormSection } from '@/components/forms/FormSection'
8
+ import { FormSelect } from '@/components/forms/FormSelect'
9
+ import { Button } from '@/components/ui/button'
10
+ import { useEntityEditor, useIO } from '@/services'
11
+ import { cn } from '@/lib/utils'
12
+
13
+ import { EntityList } from './EntityList'
14
+
15
+ export function EntityViewer({
16
+ className = '',
17
+ }: {
18
+ className?: string
19
+ } = {}) {
20
+ const entities = useTimeline((s) => s.entities)
21
+ const updateEntities = useTimeline((s) => s.updateEntities)
22
+
23
+ const saveEntitiesToClap = useIO((s) => s.saveEntitiesToClap)
24
+ const openEntitiesFromClap = useIO((s) => s.openEntitiesFromClap)
25
+
26
+ const current = useEntityEditor((s) => s.current)
27
+ const setCurrent = useEntityEditor((s) => s.setCurrent)
28
+
29
+ const draft = useEntityEditor((s) => s.draft)
30
+ const setDraft = useEntityEditor((s) => s.setDraft)
31
+
32
+ const showEntityList = useEntityEditor((s) => s.showEntityList)
33
+ const setShowEntityList = useEntityEditor((s) => s.setShowEntityList)
34
+
35
+ useEffect(() => {
36
+ setCurrent(entities.at(0))
37
+ }, [entities, setCurrent])
38
+
39
+ useEffect(() => {
40
+ setDraft(current)
41
+ }, [current, setDraft])
42
+
43
+ const handleInputChange = (
44
+ field: keyof ClapEntity,
45
+ value: string | number | undefined
46
+ ) => {
47
+ if (!draft) {
48
+ return
49
+ }
50
+ let updatedValue = value
51
+ if (field === 'age') {
52
+ updatedValue = value === '' ? undefined : parseInt(value as string)
53
+ }
54
+ if (field === 'label') {
55
+ updatedValue = value?.toString().toUpperCase()
56
+ }
57
+
58
+ setDraft({ ...draft, [field]: updatedValue })
59
+ }
60
+
61
+ const handleSave = () => {
62
+ if (!draft) {
63
+ return
64
+ }
65
+ updateEntities([draft])
66
+ }
67
+
68
+ const handleFileUpload = async (field: 'imageId' | 'audioId', file: File) => {
69
+ if (!draft) {
70
+ return
71
+ }
72
+ const dataUrl = await new Promise<string>((resolve) => {
73
+ const reader = new FileReader()
74
+ reader.onload = (e) => resolve(e.target?.result as string)
75
+ reader.readAsDataURL(file)
76
+ })
77
+ setDraft({ ...draft, [field]: dataUrl })
78
+ }
79
+
80
+ const handleExport = async () => {
81
+ if (!draft) {
82
+ return
83
+ }
84
+ await saveEntitiesToClap([draft])
85
+ }
86
+
87
+ const handleImport = async (file: File) => {
88
+ await openEntitiesFromClap(file)
89
+ }
90
+
91
+ const handleBack = () => {
92
+ setShowEntityList(true)
93
+ }
94
+
95
+ const handleSelectEntity = (entityId: string) => {
96
+ setShowEntityList(false)
97
+ }
98
+
99
+ return (
100
+ <div
101
+ className={cn(`flex h-full w-full flex-col overflow-x-auto`, className)}
102
+ >
103
+ {showEntityList ? (
104
+ <div className="mb-4">
105
+ <EntityList onSelectEntity={handleSelectEntity} />
106
+ </div>
107
+ ) : (
108
+ <div className="flex">
109
+ {draft && (
110
+ <FormSection className="px-2">
111
+ <Button onClick={handleBack}>Back</Button>
112
+ <FormInput
113
+ label="Identifier (UPPERCASE)"
114
+ value={draft.label || ''}
115
+ onChange={(value) => handleInputChange('label', value)}
116
+ />
117
+ <FormSelect<ClapSegmentCategory>
118
+ label="Category"
119
+ selectedItemId={draft.category}
120
+ items={Object.values(ClapSegmentCategory).map(
121
+ (category: ClapSegmentCategory) => ({
122
+ id: category,
123
+ label: category,
124
+ value: category,
125
+ })
126
+ )}
127
+ onSelect={(value) => handleInputChange('category', value)}
128
+ />
129
+ {/* ... form fields ... */}
130
+ <FormFile
131
+ label="Visual Identity"
132
+ onChange={(files) =>
133
+ files[0] && handleFileUpload('imageId', files[0])
134
+ }
135
+ />
136
+ {draft.imageId && (
137
+ <div className="mt-2">
138
+ <img
139
+ src={draft.imageId}
140
+ alt="Entity Preview"
141
+ className="h-auto max-w-full"
142
+ />
143
+ </div>
144
+ )}
145
+ <FormFile
146
+ label="Audio Identity"
147
+ onChange={(files) =>
148
+ files[0] && handleFileUpload('audioId', files[0])
149
+ }
150
+ />
151
+ {draft.audioId && (
152
+ <div className="mt-2">
153
+ <audio controls src={draft.audioId} />
154
+ </div>
155
+ )}
156
+ <FormInput
157
+ label="Description"
158
+ value={draft.description || ''}
159
+ onChange={(value) => handleInputChange('description', value)}
160
+ />
161
+ <FormInput
162
+ label="Appearance"
163
+ value={draft.appearance || ''}
164
+ onChange={(value) => handleInputChange('appearance', value)}
165
+ />
166
+ <FormInput
167
+ label="Age"
168
+ value={draft.age?.toString() || ''}
169
+ onChange={(value) => handleInputChange('age', value)}
170
+ />
171
+ <FormInput
172
+ label="Gender"
173
+ value={draft.gender || ''}
174
+ onChange={(value) => handleInputChange('gender', value)}
175
+ />
176
+ <div className="mt-4 flex space-x-2">
177
+ <Button onClick={handleSave}>Save</Button>
178
+ <Button onClick={handleExport}>Export</Button>
179
+ </div>
180
+ <div className="mt-4 flex space-x-2">
181
+ <FormFile
182
+ label="Import"
183
+ onChange={(files) => files[0] && handleImport(files[0])}
184
+ />
185
+ </div>
186
+ </FormSection>
187
+ )}
188
+ </div>
189
+ )}
190
+ </div>
191
+ )
192
+ }
src/components/editors/EntityEditor/index.tsx CHANGED
@@ -1,243 +1,17 @@
1
- import { useEffect } from 'react'
2
- import { ClapEntity, ClapSegmentCategory, newEntity } from '@aitube/clap'
3
- import { useTimeline } from '@aitube/timeline'
4
-
5
- import { FormFile } from '@/components/forms/FormFile'
6
- import { FormInput } from '@/components/forms/FormInput'
7
- import { FormSection } from '@/components/forms/FormSection'
8
- import { FormSelect } from '@/components/forms/FormSelect'
9
- import { Button } from '@/components/ui/button'
10
- import { useEntityEditor, useIO } from '@/services'
11
-
12
- function EntityList({
13
- onSelectEntity,
14
- }: {
15
- onSelectEntity: (entityId: string) => void
16
- }) {
17
- const entities = useTimeline((s) => s.entities)
18
- const setCurrent = useEntityEditor((s) => s.setCurrent)
19
- const addEntity = useEntityEditor((s) => s.addEntity)
20
- const removeEntity = useEntityEditor((s) => s.removeEntity)
21
-
22
- const handleAddEntity = () => {
23
- const entity: ClapEntity = newEntity({
24
- id: Date.now().toString(),
25
- label: 'NEW_ENTITY',
26
- category: ClapSegmentCategory.CHARACTER,
27
- description: '',
28
- appearance: '',
29
- }) // ignoring some fields for now
30
- addEntity(entity)
31
- }
32
-
33
- return (
34
- <div className="pt-4">
35
- <div className="mb-2">
36
- <h1 className="mb-4 inline px-4 text-xl font-bold">Entities</h1>
37
- <Button
38
- onClick={handleAddEntity}
39
- className="absolute right-2 top-2"
40
- variant="secondary"
41
- >
42
- New +
43
- </Button>
44
- </div>
45
- <ul>
46
- {entities.map((entity: ClapEntity) => (
47
- <li key={entity.id} className={`flex px-2 py-1`}>
48
- <Button
49
- onClick={() => {
50
- setCurrent(entity)
51
- onSelectEntity(entity.id)
52
- }}
53
- variant="ghost"
54
- >
55
- {entity.label} ({entity.category})
56
- </Button>
57
- <Button
58
- onClick={() => removeEntity(entity.id)}
59
- className={`ml-2 ml-auto`}
60
- variant="destructive"
61
- size="sm"
62
- >
63
- Remove
64
- </Button>
65
- </li>
66
- ))}
67
- </ul>
68
- </div>
69
- )
70
- }
71
 
72
  export function EntityEditor() {
73
- const entities = useTimeline((s) => s.entities)
74
- const updateEntities = useTimeline((s) => s.updateEntities)
75
-
76
- const saveEntitiesToClap = useIO((s) => s.saveEntitiesToClap)
77
- const openEntitiesFromClap = useIO((s) => s.openEntitiesFromClap)
78
-
79
- const current = useEntityEditor((s) => s.current)
80
- const setCurrent = useEntityEditor((s) => s.setCurrent)
81
-
82
- const draft = useEntityEditor((s) => s.draft)
83
- const setDraft = useEntityEditor((s) => s.setDraft)
84
-
85
- const showEntityList = useEntityEditor((s) => s.showEntityList)
86
- const setShowEntityList = useEntityEditor((s) => s.setShowEntityList)
87
-
88
- useEffect(() => {
89
- setCurrent(entities.at(0))
90
- }, [entities, setCurrent])
91
-
92
- useEffect(() => {
93
- setDraft(current)
94
- }, [current, setDraft])
95
-
96
- const handleInputChange = (
97
- field: keyof ClapEntity,
98
- value: string | number | undefined
99
- ) => {
100
- if (!draft) {
101
- return
102
- }
103
- let updatedValue = value
104
- if (field === 'age') {
105
- updatedValue = value === '' ? undefined : parseInt(value as string)
106
- }
107
- if (field === 'label') {
108
- updatedValue = value?.toString().toUpperCase()
109
- }
110
-
111
- setDraft({ ...draft, [field]: updatedValue })
112
- }
113
-
114
- const handleSave = () => {
115
- if (!draft) {
116
- return
117
- }
118
- updateEntities([draft])
119
- }
120
-
121
- const handleFileUpload = async (field: 'imageId' | 'audioId', file: File) => {
122
- if (!draft) {
123
- return
124
- }
125
- const dataUrl = await new Promise<string>((resolve) => {
126
- const reader = new FileReader()
127
- reader.onload = (e) => resolve(e.target?.result as string)
128
- reader.readAsDataURL(file)
129
- })
130
- setDraft({ ...draft, [field]: dataUrl })
131
- }
132
-
133
- const handleExport = async () => {
134
- if (!draft) {
135
- return
136
- }
137
- await saveEntitiesToClap([draft])
138
- }
139
-
140
- const handleImport = async (file: File) => {
141
- await openEntitiesFromClap(file)
142
- }
143
-
144
- const handleBack = () => {
145
- setShowEntityList(true)
146
- }
147
-
148
- const handleSelectEntity = (entityId: string) => {
149
- setShowEntityList(false)
150
- }
151
-
152
  return (
153
- <div className="flex h-full w-full flex-col overflow-x-auto">
154
- {showEntityList ? (
155
- <div className="mb-4">
156
- <EntityList onSelectEntity={handleSelectEntity} />
157
- </div>
158
- ) : (
159
- <div className="flex">
160
- {draft && (
161
- <FormSection className="px-2">
162
- <Button onClick={handleBack}>Back</Button>
163
- <FormInput
164
- label="Identifier (UPPERCASE)"
165
- value={draft.label || ''}
166
- onChange={(value) => handleInputChange('label', value)}
167
- />
168
- <FormSelect<ClapSegmentCategory>
169
- label="Category"
170
- selectedItemId={draft.category}
171
- items={Object.values(ClapSegmentCategory).map(
172
- (category: ClapSegmentCategory) => ({
173
- id: category,
174
- label: category,
175
- value: category,
176
- })
177
- )}
178
- onSelect={(value) => handleInputChange('category', value)}
179
- />
180
- {/* ... form fields ... */}
181
- <FormFile
182
- label="Visual Identity"
183
- onChange={(files) =>
184
- files[0] && handleFileUpload('imageId', files[0])
185
- }
186
- />
187
- {draft.imageId && (
188
- <div className="mt-2">
189
- <img
190
- src={draft.imageId}
191
- alt="Entity Preview"
192
- className="h-auto max-w-full"
193
- />
194
- </div>
195
- )}
196
- <FormFile
197
- label="Audio Identity"
198
- onChange={(files) =>
199
- files[0] && handleFileUpload('audioId', files[0])
200
- }
201
- />
202
- {draft.audioId && (
203
- <div className="mt-2">
204
- <audio controls src={draft.audioId} />
205
- </div>
206
- )}
207
- <FormInput
208
- label="Description"
209
- value={draft.description || ''}
210
- onChange={(value) => handleInputChange('description', value)}
211
- />
212
- <FormInput
213
- label="Appearance"
214
- value={draft.appearance || ''}
215
- onChange={(value) => handleInputChange('appearance', value)}
216
- />
217
- <FormInput
218
- label="Age"
219
- value={draft.age?.toString() || ''}
220
- onChange={(value) => handleInputChange('age', value)}
221
- />
222
- <FormInput
223
- label="Gender"
224
- value={draft.gender || ''}
225
- onChange={(value) => handleInputChange('gender', value)}
226
- />
227
- <div className="mt-4 flex space-x-2">
228
- <Button onClick={handleSave}>Save</Button>
229
- <Button onClick={handleExport}>Export</Button>
230
- </div>
231
- <div className="mt-4 flex space-x-2">
232
- <FormFile
233
- label="Import"
234
- onChange={(files) => files[0] && handleImport(files[0])}
235
- />
236
- </div>
237
- </FormSection>
238
- )}
239
- </div>
240
- )}
241
- </div>
242
  )
243
  }
 
1
+ import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'
2
+ import { EntityTree } from './EntityTree'
3
+ import { EntityViewer } from './EntityViewer'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  export function EntityEditor() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  return (
7
+ <ReflexContainer orientation="vertical">
8
+ <ReflexElement minSize={70} size={200}>
9
+ <EntityTree />
10
+ </ReflexElement>
11
+ <ReflexSplitter />
12
+ <ReflexElement minSize={70}>
13
+ <EntityViewer />
14
+ </ReflexElement>
15
+ </ReflexContainer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  )
17
  }
src/components/{tree-browsers/workflow-tree-browser β†’ editors/WorkflowEditor/WorkflowTree}/index.tsx RENAMED
@@ -1,18 +1,20 @@
1
  'use client'
2
 
3
- import { useEffect } from 'react'
4
-
5
  import { cn } from '@/lib/utils'
6
- import { useEntityLibrary } from '../stores/useEntityLibrary'
7
- import { LibraryNodeItem, LibraryNodeType } from '../types'
8
  import { Tree } from '@/components/core/tree'
9
 
10
- import { isClapEntity, isReplicateCollection } from '../utils/isSomething'
11
 
12
- export function WorkflowTreeBrowser() {
13
- const libraryTreeRoot = useEntityLibrary((s) => s.libraryTreeRoot)
14
- const selectTreeNode = useEntityLibrary((s) => s.selectTreeNode)
15
- const selectedTreeNodeId = useEntityLibrary((s) => s.selectedTreeNodeId)
 
 
 
 
16
 
17
  /**
18
  * handle click on tree node
@@ -26,7 +28,7 @@ export function WorkflowTreeBrowser() {
26
  const handleOnChange = async (
27
  id: string | null,
28
  nodeType?: LibraryNodeType,
29
- nodeItem?: LibraryNodeItem
30
  ) => {
31
  console.log(`calling selectTreeNodeById(id)`)
32
  selectTreeNode(id, nodeType, nodeItem)
@@ -35,10 +37,7 @@ export function WorkflowTreeBrowser() {
35
  console.log('tree-browser: clicked on an undefined node')
36
  return
37
  }
38
-
39
- if (isReplicateCollection(nodeType, nodeItem)) {
40
- // ReplicateCollection
41
- } else if (isClapEntity(nodeType, nodeItem)) {
42
  // ClapEntity
43
  } else {
44
  console.log(
@@ -50,17 +49,15 @@ export function WorkflowTreeBrowser() {
50
  }
51
 
52
  return (
53
- <div className={cn()}>
54
- <Tree.Root<LibraryNodeType, LibraryNodeItem>
55
- value={selectedTreeNodeId}
56
- onChange={handleOnChange}
57
- className="not-prose h-full w-full px-2 pt-8"
58
- label="Model Library"
59
- >
60
- {libraryTreeRoot.map((node) => (
61
- <Tree.Node node={node} key={node.id} />
62
- ))}
63
- </Tree.Root>
64
- </div>
65
  )
66
  }
 
1
  'use client'
2
 
 
 
3
  import { cn } from '@/lib/utils'
4
+ import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
5
+ import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
6
  import { Tree } from '@/components/core/tree'
7
 
8
+ import { useWorkflowTree } from './useWorkflowTree'
9
 
10
+ export function WorkflowTree({
11
+ className = '',
12
+ }: {
13
+ className?: string
14
+ } = {}) {
15
+ const libraryTreeRoot = useWorkflowTree((s) => s.libraryTreeRoot)
16
+ const selectTreeNode = useWorkflowTree((s) => s.selectTreeNode)
17
+ const selectedTreeNodeId = useWorkflowTree((s) => s.selectedTreeNodeId)
18
 
19
  /**
20
  * handle click on tree node
 
28
  const handleOnChange = async (
29
  id: string | null,
30
  nodeType?: LibraryNodeType,
31
+ nodeItem?: TreeNodeItem
32
  ) => {
33
  console.log(`calling selectTreeNodeById(id)`)
34
  selectTreeNode(id, nodeType, nodeItem)
 
37
  console.log('tree-browser: clicked on an undefined node')
38
  return
39
  }
40
+ if (isClapEntity(nodeType, nodeItem)) {
 
 
 
41
  // ClapEntity
42
  } else {
43
  console.log(
 
49
  }
50
 
51
  return (
52
+ <Tree.Root<LibraryNodeType, TreeNodeItem>
53
+ value={selectedTreeNodeId}
54
+ onChange={handleOnChange}
55
+ className={cn(`not-prose h-full w-full px-2 pt-2`, className)}
56
+ label="Workflows"
57
+ >
58
+ {libraryTreeRoot.map((node) => (
59
+ <Tree.Node node={node} key={node.id} />
60
+ ))}
61
+ </Tree.Root>
 
 
62
  )
63
  }
src/components/{tree-browsers/stores/useWorkflowLibrary.ts β†’ editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts} RENAMED
@@ -2,12 +2,20 @@
2
 
3
  import { create } from 'zustand'
4
  import { ClapEntity, UUID } from '@aitube/clap'
5
- import { LibraryNodeItem, LibraryNodeType, LibraryTreeNode } from '../types'
6
  import { icons } from '@/components/icons'
7
- import { collectionClassName, libraryClassName } from './treeNodeStyles'
 
 
 
 
 
 
 
 
8
 
9
- export const useWorkflowLibrary = create<{
10
- builtInLibraryTreeNodeId: string
11
  communityLibraryTreeNodeId: string
12
  libraryTreeRoot: LibraryTreeNode[]
13
  init: () => void
@@ -29,32 +37,32 @@ export const useWorkflowLibrary = create<{
29
  //setCommunityCollections: (collections: WorkflowCollection[]) => void
30
 
31
  // we support those all selection modes for convenience - please keep them!
32
- selectedNodeItem?: LibraryNodeItem
33
  selectedNodeType?: LibraryNodeType
34
  selectTreeNode: (
35
  treeNodeId?: string | null,
36
  nodeType?: LibraryNodeType,
37
- nodeItem?: LibraryNodeItem
38
  ) => void
39
  selectedTreeNodeId: string | null
40
  }>((set, get) => ({
41
- builtInLibraryTreeNodeId: '',
42
  communityLibraryTreeNodeId: '',
43
  libraryTreeRoot: [],
44
  init: () => {
45
- const builtInLibrary: LibraryTreeNode = {
46
  id: UUID(),
47
- nodeType: 'LIB_NODE_WORKFLOWS',
48
- label: 'Built-in workflows',
49
- icon: icons.project,
50
  className: libraryClassName,
51
  isExpanded: true,
52
  children: [
53
  {
54
  id: UUID(),
55
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
56
- label: 'A - 2',
57
- icon: icons.project,
58
  className: collectionClassName,
59
  },
60
  ],
@@ -62,25 +70,25 @@ export const useWorkflowLibrary = create<{
62
 
63
  const communityLibrary: LibraryTreeNode = {
64
  id: UUID(),
65
- nodeType: 'LIB_NODE_COMMUNITY_COLLECTION',
66
  label: 'Community workflows',
67
  icon: icons.community,
68
  className: libraryClassName,
69
  children: [
70
  {
71
  id: UUID(),
72
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
73
- label: 'A - 2',
74
  icon: icons.community,
75
  className: collectionClassName,
76
  },
77
  ],
78
  }
79
 
80
- const libraryTreeRoot = [builtInLibrary, communityLibrary]
81
 
82
  set({
83
- builtInLibraryTreeNodeId: builtInLibrary.id,
84
  communityLibraryTreeNodeId: communityLibrary.id,
85
  libraryTreeRoot,
86
  selectedNodeItem: undefined,
@@ -113,7 +121,7 @@ export const useWorkflowLibrary = create<{
113
  selectTreeNode: (
114
  treeNodeId?: string | null,
115
  nodeType?: LibraryNodeType,
116
- nodeItem?: LibraryNodeItem
117
  ) => {
118
  set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
119
  set({ selectedNodeType: nodeType ? nodeType : undefined })
@@ -121,4 +129,4 @@ export const useWorkflowLibrary = create<{
121
  },
122
  }))
123
 
124
- useWorkflowLibrary.getState().init()
 
2
 
3
  import { create } from 'zustand'
4
  import { ClapEntity, UUID } from '@aitube/clap'
5
+
6
  import { icons } from '@/components/icons'
7
+ import {
8
+ TreeNodeItem,
9
+ LibraryNodeType,
10
+ LibraryTreeNode,
11
+ } from '@/components/tree-browsers/types'
12
+ import {
13
+ collectionClassName,
14
+ libraryClassName,
15
+ } from '@/components/tree-browsers/style/treeNodeStyles'
16
 
17
+ export const useWorkflowTree = create<{
18
+ builtinLibraryTreeNodeId: string
19
  communityLibraryTreeNodeId: string
20
  libraryTreeRoot: LibraryTreeNode[]
21
  init: () => void
 
37
  //setCommunityCollections: (collections: WorkflowCollection[]) => void
38
 
39
  // we support those all selection modes for convenience - please keep them!
40
+ selectedNodeItem?: TreeNodeItem
41
  selectedNodeType?: LibraryNodeType
42
  selectTreeNode: (
43
  treeNodeId?: string | null,
44
  nodeType?: LibraryNodeType,
45
+ nodeItem?: TreeNodeItem
46
  ) => void
47
  selectedTreeNodeId: string | null
48
  }>((set, get) => ({
49
+ builtinLibraryTreeNodeId: '',
50
  communityLibraryTreeNodeId: '',
51
  libraryTreeRoot: [],
52
  init: () => {
53
+ const builtinLibrary: LibraryTreeNode = {
54
  id: UUID(),
55
+ nodeType: 'TREE_ROOT_BUILTIN',
56
+ label: 'Default workflows',
57
+ icon: icons.community,
58
  className: libraryClassName,
59
  isExpanded: true,
60
  children: [
61
  {
62
  id: UUID(),
63
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
64
+ label: 'Empty',
65
+ icon: icons.community,
66
  className: collectionClassName,
67
  },
68
  ],
 
70
 
71
  const communityLibrary: LibraryTreeNode = {
72
  id: UUID(),
73
+ nodeType: 'TREE_ROOT_COMMUNITY',
74
  label: 'Community workflows',
75
  icon: icons.community,
76
  className: libraryClassName,
77
  children: [
78
  {
79
  id: UUID(),
80
+ nodeType: 'DEFAULT_TREE_NODE_EMPTY',
81
+ label: 'Empty',
82
  icon: icons.community,
83
  className: collectionClassName,
84
  },
85
  ],
86
  }
87
 
88
+ const libraryTreeRoot = [builtinLibrary, communityLibrary]
89
 
90
  set({
91
+ builtinLibraryTreeNodeId: builtinLibrary.id,
92
  communityLibraryTreeNodeId: communityLibrary.id,
93
  libraryTreeRoot,
94
  selectedNodeItem: undefined,
 
121
  selectTreeNode: (
122
  treeNodeId?: string | null,
123
  nodeType?: LibraryNodeType,
124
+ nodeItem?: TreeNodeItem
125
  ) => {
126
  set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
127
  set({ selectedNodeType: nodeType ? nodeType : undefined })
 
129
  },
130
  }))
131
 
132
+ useWorkflowTree.getState().init()
src/components/editors/WorkflowEditor/{viewer β†’ WorkflowViewer/ReactFlowCanvas}/NodeView.tsx RENAMED
@@ -1,9 +1,9 @@
1
  import React, { memo } from 'react'
2
  import { Handle, Position } from '@xyflow/react'
3
 
4
- import { ReactWorkflowNode } from '../types'
5
  import { useTheme } from '@/services'
6
  import { cn } from '@/lib/utils'
 
7
 
8
  function NodeComponent({ data }: ReactWorkflowNode) {
9
  const theme = useTheme()
 
1
  import React, { memo } from 'react'
2
  import { Handle, Position } from '@xyflow/react'
3
 
 
4
  import { useTheme } from '@/services'
5
  import { cn } from '@/lib/utils'
6
+ import { ReactWorkflowNode } from './types'
7
 
8
  function NodeComponent({ data }: ReactWorkflowNode) {
9
  const theme = useTheme()
src/components/editors/WorkflowEditor/{clapWorkflowToReactWorkflow.ts β†’ WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts} RENAMED
@@ -1,7 +1,7 @@
1
  import { ClapWorkflow, ClapWorkflowEngine } from '@aitube/clap'
2
 
3
  import { ReactWorkflow } from './types'
4
- import { glifToReactWorkflow } from './specialized/glif/glifToReactWorkflow'
5
 
6
  export function clapWorkflowToReactWorkflow(
7
  clapWorkflow: ClapWorkflow
 
1
  import { ClapWorkflow, ClapWorkflowEngine } from '@aitube/clap'
2
 
3
  import { ReactWorkflow } from './types'
4
+ import { glifToReactWorkflow } from './formats/glif/glifToReactWorkflow'
5
 
6
  export function clapWorkflowToReactWorkflow(
7
  clapWorkflow: ClapWorkflow
src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/comfyui/types.ts RENAMED
File without changes
src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/falai/types.ts RENAMED
File without changes
src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/glif/glifToReactWorkflow.ts RENAMED
File without changes
src/components/editors/WorkflowEditor/{specialized β†’ WorkflowViewer/ReactFlowCanvas/formats}/glif/types.ts RENAMED
File without changes
src/components/editors/WorkflowEditor/{viewer/WorkflowView.tsx β†’ WorkflowViewer/ReactFlowCanvas/index.tsx} RENAMED
@@ -14,18 +14,18 @@ import {
14
  import '@xyflow/react/dist/base.css'
15
 
16
  import { NodeView } from './NodeView'
17
- import { ReactWorkflowEdge, ReactWorkflowNode } from '../types'
18
  import { useWorkflowEditor } from '@/services/editors'
19
 
20
- import { glifs } from '../samples/glif'
21
- import { glifToReactWorkflow } from '../specialized/glif/glifToReactWorkflow'
22
  import { useTheme } from '@/services'
23
 
24
  const nodeTypes = {
25
  custom: NodeView,
26
  }
27
 
28
- export function WorkflowView() {
29
  const theme = useTheme()
30
  const current = useWorkflowEditor((s) => s.current)
31
  const [nodes, setNodes, onNodesChange] = useNodesState<ReactWorkflowNode>([])
@@ -59,8 +59,27 @@ export function WorkflowView() {
59
  backgroundColor:
60
  theme.workflow.bgColor || theme.defaultBgColor || '#000000',
61
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  >
63
- <MiniMap nodeStrokeWidth={3} pannable zoomable className="scale-75" />
 
 
 
 
 
64
  <Controls />
65
  </ReactFlow>
66
  )
 
14
  import '@xyflow/react/dist/base.css'
15
 
16
  import { NodeView } from './NodeView'
17
+ import { ReactWorkflowEdge, ReactWorkflowNode } from './types'
18
  import { useWorkflowEditor } from '@/services/editors'
19
 
20
+ import { glifs } from './samples/glif'
21
+ import { glifToReactWorkflow } from './formats/glif/glifToReactWorkflow'
22
  import { useTheme } from '@/services'
23
 
24
  const nodeTypes = {
25
  custom: NodeView,
26
  }
27
 
28
+ export function ReactFlowCanvas() {
29
  const theme = useTheme()
30
  const current = useWorkflowEditor((s) => s.current)
31
  const [nodes, setNodes, onNodesChange] = useNodesState<ReactWorkflowNode>([])
 
59
  backgroundColor:
60
  theme.workflow.bgColor || theme.defaultBgColor || '#000000',
61
  }}
62
+ proOptions={{
63
+ // Since a workflow can be shown inside a tiny panel, we need to free up visual space.
64
+ // As a consequence I have hidden the React Flow attribution.
65
+ // Doing so is acceptable by their terms, since at the time of writing
66
+ // Clapper is a non-commercial project that doesn't belong to any organization.
67
+ //
68
+ // for more information about React Flow licensing and attribution:
69
+ //
70
+ // "If you start making money using React Flow or use it in an organization in the future,
71
+ // we would ask that you re-add the attribution or sign up for one of our subscriptions."
72
+ //
73
+ // https://reactflow.dev/learn/troubleshooting/remove-attribution
74
+ hideAttribution: true,
75
+ }}
76
  >
77
+ <MiniMap
78
+ nodeStrokeWidth={3}
79
+ pannable
80
+ zoomable
81
+ className="translate-x-14 translate-y-12 scale-50"
82
+ />
83
  <Controls />
84
  </ReactFlow>
85
  )
src/components/editors/WorkflowEditor/{samples β†’ WorkflowViewer/ReactFlowCanvas/samples}/glif.ts RENAMED
@@ -1,4 +1,4 @@
1
- import { GlifWorkflow } from '../specialized/glif/types'
2
 
3
  export const glifs: GlifWorkflow[] = [
4
  {
 
1
+ import { GlifWorkflow } from '../formats/glif/types'
2
 
3
  export const glifs: GlifWorkflow[] = [
4
  {
src/components/editors/WorkflowEditor/{types.ts β†’ WorkflowViewer/ReactFlowCanvas/types.ts} RENAMED
File without changes
src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { FormSection } from '@/components/forms/FormSection'
2
+ import { useWorkflowEditor } from '@/services/editors'
3
+ import { useUI } from '@/services'
4
+ import { ReactFlowCanvas } from './ReactFlowCanvas'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ export function WorkflowViewer({
8
+ className = '',
9
+ }: {
10
+ className?: string
11
+ } = {}) {
12
+ const current = useWorkflowEditor((s) => s.current)
13
+ const hasBetaAccess = useUI((s) => s.hasBetaAccess)
14
+
15
+ const content = hasBetaAccess ? (
16
+ <ReactFlowCanvas />
17
+ ) : !current ? (
18
+ <FormSection label={'Workflow Editor'} className="p-4">
19
+ Workflows are not implemented yet.
20
+ </FormSection>
21
+ ) : (
22
+ <FormSection label={'Workflow Editor'} className="p-4">
23
+ <div>Should be a form to edit the parameters.</div>
24
+ <div>
25
+ We can also display a link or an iframe with the actual workflow graph.
26
+ </div>
27
+ </FormSection>
28
+ )
29
+
30
+ return <div className={cn('h-full w-full', className)}>{content}</div>
31
+ }
src/components/editors/WorkflowEditor/index.tsx CHANGED
@@ -1,37 +1,17 @@
1
- import { useEffect } from 'react'
2
-
3
- import { FormInput } from '@/components/forms/FormInput'
4
- import { FormSection } from '@/components/forms/FormSection'
5
- import { useWorkflowEditor } from '@/services/editors'
6
- import { useUI } from '@/services'
7
- import { WorkflowView } from './viewer/WorkflowView'
8
 
9
  export function WorkflowEditor() {
10
- const current = useWorkflowEditor((s) => s.current)
11
- const setCurrent = useWorkflowEditor((s) => s.setCurrent)
12
- const history = useWorkflowEditor((s) => s.history)
13
- const undo = useWorkflowEditor((s) => s.undo)
14
- const redo = useWorkflowEditor((s) => s.redo)
15
-
16
- const hasBetaAccess = useUI((s) => s.hasBetaAccess)
17
-
18
- if (hasBetaAccess) {
19
- return <WorkflowView />
20
- }
21
- if (!current) {
22
- return (
23
- <FormSection label={'Workflow Editor'} className="p-4">
24
- Workflows are not implemented yet.
25
- </FormSection>
26
- )
27
- }
28
-
29
  return (
30
- <FormSection label={'Workflow Editor'} className="p-4">
31
- <div>Should be a form to edit the parameters.</div>
32
- <div>
33
- We can also display a link or an iframe with the actual workflow graph.
34
- </div>
35
- </FormSection>
 
 
 
36
  )
37
  }
 
1
+ import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'
2
+ import { WorkflowTree } from './WorkflowTree'
3
+ import { WorkflowViewer } from './WorkflowViewer'
 
 
 
 
4
 
5
  export function WorkflowEditor() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  return (
7
+ <ReflexContainer orientation="vertical">
8
+ <ReflexElement minSize={70} size={200}>
9
+ <WorkflowTree />
10
+ </ReflexElement>
11
+ <ReflexSplitter />
12
+ <ReflexElement minSize={70}>
13
+ <WorkflowViewer />
14
+ </ReflexElement>
15
+ </ReflexContainer>
16
  )
17
  }
src/components/editors/WorkflowEditor/viewer/README.md DELETED
@@ -1 +0,0 @@
1
- # Workflow
 
 
src/components/forms/index.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export { FormDir } from './FormDir'
2
+ export { FormField } from './FormField'
3
+ export { FormFile } from './FormFile'
4
+ export { FormInput } from './FormInput'
5
+ export { FormLabel } from './FormLabel'
6
+ export { FormRadio } from './FormRadio'
7
+ export { FormSection } from './FormSection'
8
+ export { FormSelect } from './FormSelect'
9
+ export { FormSwitch } from './FormSwitch'
src/components/toolbars/top-bar/index.tsx CHANGED
@@ -1,24 +1,31 @@
1
- import { ClapProject } from '@aitube/clap'
2
- import { useTimeline } from '@aitube/timeline'
3
 
 
4
  import { cn } from '@/lib/utils'
5
 
6
  import { TopMenu } from '../top-menu'
7
 
8
  export function TopBar() {
9
- const clap: ClapProject = useTimeline((s) => s.clap)
10
 
11
  return (
12
  <div
13
- className={cn(
14
- `flex flex-row`,
15
- `h-9 w-full`,
16
- `items-center bg-stone-900`,
17
- `border-b`,
18
- `border-b-stone-700`
19
- )}
20
  >
21
- <TopMenu />
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
  )
24
  }
 
1
+ 'use client'
 
2
 
3
+ import { useTheme } from '@/services'
4
  import { cn } from '@/lib/utils'
5
 
6
  import { TopMenu } from '../top-menu'
7
 
8
  export function TopBar() {
9
+ const theme = useTheme()
10
 
11
  return (
12
  <div
13
+ className={cn(`h-9 w-full`)}
14
+ style={{
15
+ backgroundColor: theme.defaultBgColor || '#000000',
16
+ }}
 
 
 
17
  >
18
+ <div
19
+ className={cn(
20
+ `flex flex-row`,
21
+ `h-full w-full`,
22
+ `items-center bg-neutral-900/30`,
23
+ `border-b`,
24
+ `border-b-neutral-700/30`
25
+ )}
26
+ >
27
+ <TopMenu />
28
+ </div>
29
  </div>
30
  )
31
  }
src/components/toolbars/top-menu/index.tsx CHANGED
@@ -40,7 +40,7 @@ export function TopMenu() {
40
 
41
  return (
42
  <Menubar
43
- className="ml-1 w-full"
44
  onValueChange={(value) => {
45
  setIsTopMenuOpen(!!value)
46
  }}
 
40
 
41
  return (
42
  <Menubar
43
+ className="w-full pl-2"
44
  onValueChange={(value) => {
45
  setIsTopMenuOpen(!!value)
46
  }}
src/components/tree-browsers/model-tree-browser/index.tsx DELETED
@@ -1,88 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect } from 'react'
4
-
5
- import { cn } from '@/lib/utils'
6
- import { useEntityLibrary } from '../stores/useEntityLibrary'
7
- import { LibraryNodeItem, LibraryNodeType } from '../types'
8
- import { Tree } from '@/components/core/tree'
9
-
10
- import { isClapEntity, isReplicateCollection } from '../utils/isSomething'
11
- import { useCivitaiCollections } from '../stores/useCivitaiCollections'
12
- import { useReplicateCollections } from '../stores/useReplicateCollections'
13
-
14
- export function ModelTreeBrowser() {
15
- const libraryTreeRoot = useEntityLibrary((s) => s.libraryTreeRoot)
16
- const selectTreeNode = useEntityLibrary((s) => s.selectTreeNode)
17
- const selectedTreeNodeId = useEntityLibrary((s) => s.selectedTreeNodeId)
18
- const setReplicateCollections = useEntityLibrary(
19
- (s) => s.setReplicateCollections
20
- )
21
- const setCivitaiCollections = useEntityLibrary((s) => s.setCivitaiCollections)
22
-
23
- // TODO: we are forced to do this because the api "endpoint" is a server action
24
- // however we could rewrite it so that we can pull the collections directly
25
- // from the Zustand store
26
-
27
- const newReplicateCollections = useReplicateCollections()
28
- useEffect(() => {
29
- setReplicateCollections(newReplicateCollections)
30
- // eslint-disable-next-line
31
- }, [JSON.stringify(newReplicateCollections)]) // ... yeah, I know, I know..
32
-
33
- const newCivitaiCollections = useCivitaiCollections()
34
- useEffect(() => {
35
- setCivitaiCollections(newCivitaiCollections)
36
- // eslint-disable-next-line
37
- }, [JSON.stringify(newCivitaiCollections)]) // ... yeah, I know, I know..
38
-
39
- /**
40
- * handle click on tree node
41
- * yes, this is where the magic happens!
42
- *
43
- * @param id
44
- * @param nodeType
45
- * @param node
46
- * @returns
47
- */
48
- const handleOnChange = async (
49
- id: string | null,
50
- nodeType?: LibraryNodeType,
51
- nodeItem?: LibraryNodeItem
52
- ) => {
53
- console.log(`calling selectTreeNodeById(id)`)
54
- selectTreeNode(id, nodeType, nodeItem)
55
-
56
- if (!nodeType || !nodeItem) {
57
- console.log('tree-browser: clicked on an undefined node')
58
- return
59
- }
60
-
61
- if (isReplicateCollection(nodeType, nodeItem)) {
62
- // ReplicateCollection
63
- } else if (isClapEntity(nodeType, nodeItem)) {
64
- // ClapEntity
65
- } else {
66
- console.log(
67
- `tree-browser: no action attached to ${nodeType}, so skipping`
68
- )
69
- return
70
- }
71
- console.log(`tree-browser: clicked on a ${nodeType}`, nodeItem)
72
- }
73
-
74
- return (
75
- <div className={cn()}>
76
- <Tree.Root<LibraryNodeType, LibraryNodeItem>
77
- value={selectedTreeNodeId}
78
- onChange={handleOnChange}
79
- className="not-prose h-full w-full px-2 pt-8"
80
- label="Model Library"
81
- >
82
- {libraryTreeRoot.map((node) => (
83
- <Tree.Node node={node} key={node.id} />
84
- ))}
85
- </Tree.Root>
86
- </div>
87
- )
88
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx DELETED
@@ -1,17 +0,0 @@
1
- 'use client'
2
-
3
- import { useEntityLibrary } from '../stores/useEntityLibrary'
4
-
5
- export function TreeItemViewer() {
6
- const selectedNodeItem = useEntityLibrary((s) => s.selectedNodeItem)
7
- const selectedNodeType = useEntityLibrary((s) => s.selectedNodeType)
8
-
9
- const nodeType = selectedNodeType
10
- const data = selectedNodeItem
11
-
12
- if (!nodeType || !data) {
13
- return null
14
- }
15
-
16
- return <div className="pt-8">The selected item cannot be preview.</div>
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx DELETED
@@ -1,17 +0,0 @@
1
- 'use client'
2
-
3
- import { useEntityLibrary } from '../stores/useEntityLibrary'
4
-
5
- export function TreeItemViewer() {
6
- const selectedNodeItem = useEntityLibrary((s) => s.selectedNodeItem)
7
- const selectedNodeType = useEntityLibrary((s) => s.selectedNodeType)
8
-
9
- const nodeType = selectedNodeType
10
- const data = selectedNodeItem
11
-
12
- if (!nodeType || !data) {
13
- return null
14
- }
15
-
16
- return <div>TODO</div>
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/stores/useCivitaiCollections.ts DELETED
@@ -1,24 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState, useTransition } from 'react'
4
- import { CivitaiCollection } from '../types'
5
-
6
- export function useCivitaiCollections(): CivitaiCollection[] {
7
- const [_pending, startTransition] = useTransition()
8
- const [collections, setCollections] = useState<CivitaiCollection[]>([])
9
- // const [models, setModels] = useState<CivitaiModel[]>([])
10
-
11
- useEffect(() => {
12
- startTransition(async () => {
13
- // TODO @Julian: this was something we did in the ligacy
14
- // Clapper, but I'm not sure we want to support Civitai
15
- // again just yet, we probably require other arch changes
16
- // const collections = await listCollections()
17
- const collections: CivitaiCollection[] = []
18
- setCollections(collections)
19
- // setModels(models)
20
- })
21
- }, [])
22
-
23
- return collections
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/stores/useEntityLibrary.ts DELETED
@@ -1,336 +0,0 @@
1
- 'use client'
2
-
3
- import { create } from 'zustand'
4
- import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap'
5
- import {
6
- CivitaiCollection,
7
- LibraryNodeItem,
8
- LibraryNodeType,
9
- LibraryTreeNode,
10
- ReplicateCollection,
11
- } from '../types'
12
- import { icons } from '@/components/icons'
13
- import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
14
- import {
15
- collectionClassName,
16
- itemClassName,
17
- libraryClassName,
18
- } from './treeNodeStyles'
19
-
20
- export const useEntityLibrary = create<{
21
- teamLibraryTreeNodeId: string
22
- communityLibraryTreeNodeId: string
23
- civitaiLibraryTreeNodeId: string
24
- huggingfaceLibraryTreeNodeId: string
25
- replicateLibraryTreeNodeId: string
26
- libraryTreeRoot: LibraryTreeNode[]
27
- init: () => void
28
-
29
- /**
30
- * Load Replicate collections (API models) into the tree
31
- *
32
- * @param collections
33
- * @returns
34
- */
35
- setReplicateCollections: (collections: ReplicateCollection[]) => void
36
-
37
- /**
38
- * Load Replicate collections (LoRA models) into the tree
39
- *
40
- * @param collections
41
- * @returns
42
- */
43
- setCivitaiCollections: (collections: CivitaiCollection[]) => void
44
-
45
- // we support those all selection modes for convenience - please keep them!
46
- selectedNodeItem?: LibraryNodeItem
47
- selectedNodeType?: LibraryNodeType
48
- selectTreeNode: (
49
- treeNodeId?: string | null,
50
- nodeType?: LibraryNodeType,
51
- nodeItem?: LibraryNodeItem
52
- ) => void
53
- selectedTreeNodeId: string | null
54
- }>((set, get) => ({
55
- localUserLibraryTreeNodeId: '',
56
- huggingfaceUserLibraryTreeNodeId: '',
57
- teamLibraryTreeNodeId: '',
58
- communityLibraryTreeNodeId: '',
59
- civitaiLibraryTreeNodeId: '',
60
- huggingfaceLibraryTreeNodeId: '',
61
- replicateLibraryTreeNodeId: '',
62
- libraryTreeRoot: [],
63
- init: () => {
64
- const teamLibrary: LibraryTreeNode = {
65
- id: UUID(),
66
- nodeType: 'LIB_NODE_TEAM_COLLECTION',
67
- label: 'Team models',
68
- icon: icons.team,
69
- className: libraryClassName,
70
- isExpanded: true,
71
- children: [
72
- {
73
- id: UUID(),
74
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
75
- label: 'A - 2',
76
- icon: icons.team,
77
- className: collectionClassName,
78
- },
79
- ],
80
- }
81
-
82
- const civitaiLibrary: LibraryTreeNode = {
83
- id: UUID(),
84
- nodeType: 'LIB_NODE_CIVITAI_COLLECTION',
85
- label: 'Civitai models',
86
- icon: icons.community,
87
- className: libraryClassName,
88
- children: [],
89
- }
90
-
91
- const communityLibrary: LibraryTreeNode = {
92
- id: UUID(),
93
- nodeType: 'LIB_NODE_COMMUNITY_COLLECTION',
94
- label: 'Community models',
95
- icon: icons.community,
96
- className: libraryClassName,
97
- children: [
98
- {
99
- id: UUID(),
100
- nodeType: 'LIB_NODE_GENERIC_EMPTY',
101
- label: 'A - 2',
102
- icon: icons.community,
103
- className: collectionClassName,
104
- },
105
- ],
106
- }
107
-
108
- const huggingfaceLibrary: LibraryTreeNode = {
109
- id: UUID(),
110
- nodeType: 'LIB_NODE_HUGGINGFACE_COLLECTION',
111
- label: 'Hugging Face',
112
- icon: icons.vendor,
113
- className: libraryClassName,
114
- children: [],
115
- }
116
-
117
- const replicateLibrary: LibraryTreeNode = {
118
- id: UUID(),
119
- nodeType: 'LIB_NODE_REPLICATE_COLLECTION',
120
- label: 'Replicate',
121
- icon: icons.vendor,
122
- isExpanded: false, // This node is expanded by default
123
- className: libraryClassName,
124
- children: [],
125
- }
126
-
127
- const libraryTreeRoot = [
128
- // teamLibrary,
129
- // communityLibrary,
130
- civitaiLibrary,
131
- // huggingfaceLibrary,
132
- replicateLibrary,
133
- ]
134
-
135
- set({
136
- teamLibraryTreeNodeId: teamLibrary.id,
137
- civitaiLibraryTreeNodeId: civitaiLibrary.id,
138
- communityLibraryTreeNodeId: communityLibrary.id,
139
- huggingfaceLibraryTreeNodeId: huggingfaceLibrary.id,
140
- replicateLibraryTreeNodeId: replicateLibrary.id,
141
- libraryTreeRoot,
142
- selectedNodeItem: undefined,
143
- selectedTreeNodeId: null,
144
- })
145
- },
146
-
147
- setProjectEntities: async (entities: ClapEntity[]) => {
148
- const characters: LibraryTreeNode = {
149
- id: UUID(),
150
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
151
- data: undefined,
152
- label: 'Characters',
153
- icon: icons.characters,
154
- className: collectionClassName,
155
- isExpanded: true, // This node is expanded by default
156
- children: [],
157
- }
158
-
159
- const locations: LibraryTreeNode = {
160
- id: UUID(),
161
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
162
- data: undefined,
163
- label: 'Locations',
164
- icon: icons.location,
165
- className: collectionClassName,
166
- isExpanded: false, // This node is expanded by default
167
- children: [],
168
- }
169
-
170
- const misc: LibraryTreeNode = {
171
- id: UUID(),
172
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
173
- data: undefined,
174
- label: 'Misc',
175
- icon: icons.misc,
176
- className: collectionClassName,
177
- isExpanded: false, // This node is expanded by default
178
- children: [],
179
- }
180
-
181
- entities.forEach((entity) => {
182
- const node: LibraryTreeNode = {
183
- nodeType: 'LIB_NODE_REPLICATE_MODEL',
184
- id: entity.id,
185
- data: entity,
186
- label: entity.label,
187
- icon: icons.misc,
188
- className: itemClassName,
189
- }
190
- if (entity.category === ClapSegmentCategory.CHARACTER) {
191
- node.icon = icons.character
192
- characters.children!.push(node)
193
- } else if (entity.category === ClapSegmentCategory.LOCATION) {
194
- node.icon = icons.location
195
- locations.children!.push(node)
196
- } else {
197
- misc.children!.push(node)
198
- }
199
- })
200
- },
201
-
202
- setReplicateCollections: (collections: ReplicateCollection[]) => {
203
- const { replicateLibraryTreeNodeId, libraryTreeRoot } = get()
204
-
205
- set({
206
- libraryTreeRoot: libraryTreeRoot.map((node) => {
207
- if (node.id !== replicateLibraryTreeNodeId) {
208
- return node
209
- }
210
-
211
- return {
212
- ...node,
213
-
214
- children:
215
- // only keep non-empty models
216
- collections
217
- .filter((c) => c.models.length)
218
-
219
- // only visual or sound oriented models
220
- .filter((c) => {
221
- const name = c.name.toLowerCase()
222
-
223
- // ignore captioning models, we don't need this right now
224
- if (name.includes('to text') || name.includes('to-text')) {
225
- return false
226
- }
227
-
228
- if (
229
- name.includes('image') ||
230
- name.includes('video') ||
231
- name.includes('style') ||
232
- name.includes('audio') ||
233
- name.includes('sound') ||
234
- name.includes('music') ||
235
- name.includes('speech') ||
236
- name.includes('voice') ||
237
- name.includes('resolution') ||
238
- name.includes('upscale') ||
239
- name.includes('upscaling') ||
240
- name.includes('interpolate') ||
241
- name.includes('interpolation')
242
- ) {
243
- return true
244
- }
245
- return false
246
- })
247
- .map<LibraryTreeNode>((c) => ({
248
- id: UUID(),
249
- data: c,
250
- nodeType: 'LIB_NODE_REPLICATE_COLLECTION',
251
- label: c.name,
252
- icon: getAppropriateIcon(c.name),
253
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
254
- isExpanded: false, // This node is expanded by default
255
- children: c.models.map<LibraryTreeNode>((m) => ({
256
- nodeType: 'LIB_NODE_REPLICATE_MODEL',
257
- id: m.id,
258
- data: m,
259
- label: m.label,
260
- icon: getAppropriateIcon(m.label, getAppropriateIcon(c.name)),
261
- className: itemClassName, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
262
- })),
263
- })),
264
- }
265
- }),
266
- })
267
- },
268
-
269
- setCivitaiCollections: (collections: CivitaiCollection[]) => {
270
- const { civitaiLibraryTreeNodeId, libraryTreeRoot } = get()
271
-
272
- set({
273
- libraryTreeRoot: libraryTreeRoot.map((node) => {
274
- if (node.id !== civitaiLibraryTreeNodeId) {
275
- return node
276
- }
277
-
278
- return {
279
- ...node,
280
-
281
- children: collections.map<LibraryTreeNode>((c) => ({
282
- id: UUID(),
283
- data: c,
284
- nodeType: 'LIB_NODE_CIVITAI_COLLECTION',
285
- label: c.name,
286
- icon: getAppropriateIcon(c.name),
287
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
288
- isExpanded: false, // This node is expanded by default
289
- children: c.models.map<LibraryTreeNode>((m) => ({
290
- nodeType: 'LIB_NODE_CIVITAI_MODEL',
291
- id: m.id,
292
- data: m,
293
- label: m.label,
294
- icon: getAppropriateIcon(m.label, getAppropriateIcon(c.name)),
295
- className: itemClassName, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
296
- })),
297
- })),
298
- }
299
- }),
300
- })
301
- },
302
-
303
- selectedNodeItem: undefined,
304
- selectEntity: (entity?: ClapEntity) => {
305
- if (entity) {
306
- console.log(
307
- 'TODO julian: change this code to search in the entity collections'
308
- )
309
- const selectedTreeNode = get().libraryTreeRoot.find(
310
- (node) => node.data?.id === entity.id
311
- )
312
-
313
- // set({ selectedTreeNode })
314
- set({ selectedTreeNodeId: selectedTreeNode?.id || null })
315
- set({ selectedNodeItem: entity })
316
- } else {
317
- // set({ selectedTreeNode: undefined })
318
- set({ selectedTreeNodeId: null })
319
- set({ selectedNodeItem: undefined })
320
- }
321
- },
322
-
323
- // selectedTreeNode: undefined,
324
- selectedTreeNodeId: null,
325
- selectTreeNode: (
326
- treeNodeId?: string | null,
327
- nodeType?: LibraryNodeType,
328
- nodeItem?: LibraryNodeItem
329
- ) => {
330
- set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
331
- set({ selectedNodeType: nodeType ? nodeType : undefined })
332
- set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
333
- },
334
- }))
335
-
336
- useEntityLibrary.getState().init()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/stores/useFileLibrary.txt DELETED
@@ -1,204 +0,0 @@
1
- "use client"
2
-
3
- import { create } from "zustand"
4
-
5
- import { ClapEntity, UUID } from "@aitube/clap"
6
- import { HuggingFaceUserCollection, LibraryNodeItem, LibraryNodeType, LibraryTreeNode, LocalUserCollection } from "../types"
7
- import { icons } from "@/components/icons"
8
- import { getAppropriateIcon } from "@/components/icons/getAppropriateIcon"
9
- import { className } from "@/app/fonts"
10
- import { getCollectionItemTextColor } from "../utils/getCollectionItemTextColor"
11
- import { collectionClassName, itemClassName, libraryClassName } from './treeNodeStyles'
12
-
13
- export const useFileLibrary = create<{
14
- localUserLibraryTreeNodeId: string
15
- huggingfaceUserLibraryTreeNodeId: string
16
- libraryTreeRoot: LibraryTreeNode[]
17
- init: () => void
18
-
19
- /**
20
- * Load local user collections (projects, assets) into the tree
21
- *
22
- * @param collections
23
- * @returns
24
- */
25
- setLocalUserCollections: (collections: LocalUserCollection[]) => void
26
-
27
- /**
28
- * Load Hugging Face user collections (projects, assets) into the tree
29
- *
30
- * @param collections
31
- * @returns
32
- */
33
- setHuggingFaceUserCollections: (collections: HuggingFaceUserCollection[]) => void
34
-
35
- // we support those all selection modes for convenience - please keep them!
36
- selectedNodeItem?: LibraryNodeItem
37
- selectedNodeType?: LibraryNodeType
38
- selectTreeNode: (treeNodeId?: string | null, nodeType?: LibraryNodeType, nodeItem?: LibraryNodeItem) => void
39
- selectedTreeNodeId: string | null
40
- }>((set, get) => ({
41
- localUserLibraryTreeNodeId: "",
42
- huggingfaceUserLibraryTreeNodeId: "",
43
- libraryTreeRoot: [],
44
- init: () => {
45
-
46
- const localUserLibrary: LibraryTreeNode = {
47
- id: UUID(),
48
- nodeType: "LIB_NODE_LOCAL_USER_COLLECTION",
49
- label: "My Computer",
50
- icon: icons.computer,
51
- className: libraryClassName,
52
- isExpanded: true, // This node is expanded by default
53
- children: [
54
- {
55
- id: UUID(),
56
- nodeType: "LIB_NODE_GENERIC_EMPTY",
57
- label: "(No files to display)",
58
- icon: icons.misc,
59
- className: `${collectionClassName} text-gray-100/30`,
60
- isExpanded: false, // This node is expanded by default
61
- children: []
62
- }
63
- ]
64
- }
65
-
66
- const huggingfaceUserLibrary: LibraryTreeNode = {
67
- id: UUID(),
68
- nodeType: "LIB_NODE_HUGGINGFACE_USER_COLLECTION",
69
- label: "My HF Cloud",
70
- icon: icons.cloud,
71
- className: libraryClassName,
72
- isExpanded: true, // This node is expanded by default
73
- children: [
74
- {
75
- id: UUID(),
76
- nodeType: "LIB_NODE_GENERIC_EMPTY",
77
- label: "(No files to display)",
78
- icon: icons.misc,
79
- className: `${collectionClassName} text-gray-100/30`,
80
- isExpanded: false, // This node is expanded by default
81
- children: []
82
- }
83
- ]
84
- }
85
-
86
- const libraryTreeRoot = [
87
- localUserLibrary,
88
- huggingfaceUserLibrary,
89
- ]
90
-
91
- set({
92
- localUserLibraryTreeNodeId: localUserLibrary.id,
93
- huggingfaceUserLibraryTreeNodeId: huggingfaceUserLibrary.id,
94
- libraryTreeRoot,
95
- selectedNodeItem: undefined,
96
- selectedTreeNodeId: null,
97
- })
98
- },
99
-
100
- setLocalUserCollections: (collections: LocalUserCollection[]) => {
101
- const { localUserLibraryTreeNodeId, libraryTreeRoot } = get()
102
-
103
- console.log("setLocalUserCollections:", collections)
104
-
105
- set({
106
- libraryTreeRoot: libraryTreeRoot.map(node => {
107
- if (node.id !== localUserLibraryTreeNodeId) { return node }
108
-
109
- return {
110
- ...node,
111
-
112
- children: collections.map<LibraryTreeNode>(c => ({
113
- id: UUID(),
114
- nodeType: "LIB_NODE_LOCAL_USER_FOLDER",
115
- data: c,
116
- label: c.name, // file directory name
117
- icon: getAppropriateIcon(c.name),
118
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
119
- isExpanded: false, // This node is expanded by default
120
- children: c.items.map<LibraryTreeNode>(m => ({
121
- nodeType: "LIB_NODE_LOCAL_USER_FILE",
122
- id: m.id,
123
- data: m,
124
- label: <><span>{
125
- m.fileName.split(".").slice(0, -1)
126
- }</span><span className="opacity-50">{
127
- `.${m.fileName.split(".").pop()}`
128
- }</span></>,
129
- icon: getAppropriateIcon(m.fileName, getAppropriateIcon(c.name)),
130
- className: `${itemClassName} ${
131
- getCollectionItemTextColor(m.fileName)
132
- }`, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
133
- }))
134
- }))
135
- }
136
- })
137
- })
138
- },
139
-
140
- setHuggingFaceUserCollections: (collections: HuggingFaceUserCollection[]) => {
141
- const { huggingfaceUserLibraryTreeNodeId, libraryTreeRoot } = get()
142
-
143
- set({
144
- libraryTreeRoot: libraryTreeRoot.map(node => {
145
- if (node.id !== huggingfaceUserLibraryTreeNodeId) { return node }
146
-
147
- return {
148
- ...node,
149
-
150
- children: collections.map<LibraryTreeNode>(c => ({
151
- id: c.id,
152
- nodeType: "LIB_NODE_HUGGINGFACE_USER_DATASET",
153
- data: c,
154
- label: c.name, // file directory name
155
- icon: getAppropriateIcon(c.name),
156
- className: collectionClassName, // `${collectionClassName} ${getCollectionColor(c.name)}`,
157
- isExpanded: false, // This node is expanded by default
158
- children: c.items.map<LibraryTreeNode>(m => ({
159
- nodeType: "LIB_NODE_HUGGINGFACE_USER_FILE",
160
- id: m.id,
161
- data: m,
162
- label: <><span>{
163
- m.fileName.split(".").slice(0, -1)
164
- }</span><span className="opacity-50">{
165
- `.${m.fileName.split(".").pop()}`
166
- }</span></>,
167
- icon: getAppropriateIcon(m.fileName, getAppropriateIcon(c.name)),
168
- className: `${itemClassName} ${
169
- getCollectionItemTextColor(m.fileName)
170
- }`, // `${itemClassName} ${getItemColor(m.label, getItemColor(c.name))}`,
171
- }))
172
- }))
173
- }
174
- })
175
- })
176
- },
177
-
178
- selectedNodeItem: undefined,
179
- selectEntity: (entity?: ClapEntity) => {
180
- if (entity) {
181
- console.log("TODO julian: change this code to search in the entity collections")
182
- const selectedTreeNode =
183
- get().libraryTreeRoot
184
- .find(node => node.data?.id === entity.id)
185
-
186
- // set({ selectedTreeNode })
187
- set({ selectedTreeNodeId: selectedTreeNode?.id || null })
188
- set({ selectedNodeItem: entity })
189
- } else {
190
- // set({ selectedTreeNode: undefined })
191
- set({ selectedTreeNodeId: null })
192
- set({ selectedNodeItem: undefined })
193
- }
194
- },
195
- // selectedTreeNode: undefined,
196
- selectedTreeNodeId: null,
197
- selectTreeNode: (treeNodeId?: string | null, nodeType?: LibraryNodeType, nodeItem?: LibraryNodeItem) => {
198
- set({ selectedTreeNodeId: treeNodeId ? treeNodeId : undefined })
199
- set({ selectedNodeType: nodeType ? nodeType : undefined })
200
- set({ selectedNodeItem: nodeItem ? nodeItem : undefined })
201
- }
202
- }))
203
-
204
- useFileLibrary.getState().init()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/stores/useReplicateCollections.ts DELETED
@@ -1,24 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState, useTransition } from 'react'
4
- import { ReplicateCollection } from '../types'
5
-
6
- export function useReplicateCollections(): ReplicateCollection[] {
7
- const [_pending, startTransition] = useTransition()
8
- const [collections, setCollections] = useState<ReplicateCollection[]>([])
9
- // const [models, setModels] = useState<ReplicateModel[]>([])
10
-
11
- useEffect(() => {
12
- startTransition(async () => {
13
- // TODO @Julian: this was something we did in the ligacy
14
- // Clapper, but I'm not sure we want to support Replicate
15
- // again just yet, we probably require other arch changes
16
- // const collections = await listCollections()
17
- const collections: ReplicateCollection[] = []
18
- setCollections(collections)
19
- // setModels(models)
20
- })
21
- }, [])
22
-
23
- return collections
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/tree-browsers/{stores β†’ style}/treeNodeStyles.ts RENAMED
@@ -1,8 +1,10 @@
1
  // TODO: this isn't the best place for this as this is style,
2
  // and we are in a state manager
3
- export const libraryClassName = 'text-base font-semibold'
 
4
 
5
- export const collectionClassName = `text-base font-normal`
 
6
 
7
  export const itemClassName =
8
- 'text-sm font-light text-gray-200/60 hover:text-gray-200/100'
 
1
  // TODO: this isn't the best place for this as this is style,
2
  // and we are in a state manager
3
+ export const libraryClassName =
4
+ 'text-sm font-semibold text-white/80 hover:text-white/90'
5
 
6
+ export const collectionClassName =
7
+ 'text-sm font-normal text-white/70 hover:text-white/90'
8
 
9
  export const itemClassName =
10
+ 'text-xs font-light text-white/60 hover:text-white/90'
src/components/tree-browsers/types.ts CHANGED
@@ -4,67 +4,72 @@ import { ScreenplaySequence } from '@aitube/broadway'
4
  import { ClapEntity, ClapSegment, ClapWorkflow } from '@aitube/clap'
5
  import { TreeNodeType } from '../core/tree/types'
6
 
7
- // not sure if we should also sort them into data type categories,
8
- // as vendors like to be on multiple kind of models
9
-
10
- export type LibraryNodeHuggingFaceType =
11
- | 'LIB_NODE_HUGGINGFACE_USER_COLLECTION'
12
- | 'LIB_NODE_HUGGINGFACE_USER_DATASET'
13
-
14
- export type LibraryNodeLocalFileType =
15
- | 'LIB_NODE_LOCAL_USER_FILE'
16
- | 'LIB_NODE_LOCAL_USER_FOLDER'
17
-
18
- export type LibraryNodeRemoteFileType =
19
- | 'LIB_NODE_HUGGINGFACE_USER_FOLDER'
20
- | 'LIB_NODE_HUGGINGFACE_USER_FILE'
21
-
22
- export type LibraryNodeFileType =
23
- | LibraryNodeLocalFileType
24
- | LibraryNodeRemoteFileType
25
-
26
- export type LibraryNodeWorkflowType = 'LIB_NODE_WORKFLOWS' | 'LIB_NODE_WORKFLOW'
27
-
28
- export type LibraryNodeProjectType =
29
- | 'LIB_NODE_PROJECT_COLLECTION'
30
- | 'LIB_NODE_PROJECT_ARCHIVE'
31
- | 'LIB_NODE_PROJECT_ASSET' // image, sound file..
32
- | 'LIB_NODE_PROJECT_ENTITY_GENERIC'
33
- | 'LIB_NODE_PROJECT_ENTITY_CHARACTER'
34
- | 'LIB_NODE_PROJECT_ENTITY_LOCATION'
35
-
36
- export type LibraryNodeGenericType =
37
- | 'LIB_NODE_GENERIC_COLLECTION'
38
- | 'LIB_NODE_GENERIC_MODEL'
39
- | 'LIB_NODE_GENERIC_ITEM'
40
- | 'LIB_NODE_GENERIC_EMPTY'
41
-
42
- /**
43
- * we could use "LIB_NODE_GENERIC_COLLECTION",
44
- * but I think it can also be useful to keep specific types,
45
- * that way we can show a custom collection UI panel on the right of the explorer
46
- */
47
- export type LibraryNodeType =
48
- | 'LIB_NODE_LOCAL_USER_COLLECTION'
49
- | LibraryNodeLocalFileType
50
- | LibraryNodeHuggingFaceType
51
- | LibraryNodeRemoteFileType
52
- | LibraryNodeWorkflowType
53
- | LibraryNodeProjectType
54
- | 'LIB_NODE_TEAM_COLLECTION'
55
- | 'LIB_NODE_TEAM_MODEL'
56
- | 'LIB_NODE_COMMUNITY_COLLECTION'
57
- | 'LIB_NODE_COMMUNITY_MODEL'
58
- | 'LIB_NODE_HUGGINGFACE_COLLECTION'
59
- | 'LIB_NODE_HUGGINGFACE_MODEL'
60
- | 'LIB_NODE_REPLICATE_COLLECTION'
61
- | 'LIB_NODE_REPLICATE_MODEL'
62
- | 'LIB_NODE_CIVITAI_COLLECTION'
63
- | 'LIB_NODE_CIVITAI_MODEL'
64
- | LibraryNodeGenericType
 
 
 
 
 
65
 
66
  // can be a file or folder
67
- export type LocalUserItem = {
68
  id: string // can be the path for now
69
  path: string // path to the file content
70
  fileName: string
@@ -73,19 +78,19 @@ export type LocalUserItem = {
73
  extension: string
74
  mimetype: string
75
  isDirectory: boolean
76
- items: LocalUserItem[]
77
  }
78
 
79
- export type LocalUserCollection = {
80
  id: string
81
  name: string
82
  description: string
83
  path: string // path to the root directory (eg "~/Clapper")
84
- items: LocalUserItem[] // files or folders
85
  }
86
 
87
  // can be a file or folder
88
- export type HuggingFaceUserItem = {
89
  id: string // can be the path for now (so not a uuid)
90
  assetUrl: string // full URL to download the content (eg. to use with wget)
91
  datasetName: string
@@ -99,18 +104,25 @@ export type HuggingFaceUserItem = {
99
  mimeType: string
100
  isDirectory: boolean
101
  isPrivate: boolean
102
- items: HuggingFaceUserItem[]
103
  }
104
 
105
- export type HuggingFaceUserCollection = {
106
- id: string // huggingface ud (not a uuid)
107
  name: string
108
  description: string
109
  userName: string
110
  datasetName: string
111
  repository: string
112
  url: string
113
- items: HuggingFaceUserItem[] // files or folders
 
 
 
 
 
 
 
114
  }
115
 
116
  export type ReplicateModel = {
@@ -158,24 +170,38 @@ export type WorkflowCollection = {
158
  }
159
 
160
  // TODO unify this a bit, at least in the naming scheme
161
- export type LibraryNodeFileItem = LocalUserItem | HuggingFaceUserItem
 
 
 
 
 
 
 
 
 
 
162
 
163
  // TODO unify this a bit, at least in the naming scheme
164
- export type LibraryNodeItem =
165
  | ClapEntity
166
- | ReplicateCollection
167
- | ReplicateModel
168
- | CivitaiCollection
169
- | CivitaiModel
170
- | LocalUserCollection
171
- | LocalUserItem
172
- | HuggingFaceUserCollection
173
- | HuggingFaceUserItem
174
- | ScreenplaySequence
175
  | ClapSegment
176
- | WorkflowCollection
177
  | ClapWorkflow
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  // a model library is a collection of models
180
  // this collection can itself include sub-models
181
- export type LibraryTreeNode = TreeNodeType<LibraryNodeType, LibraryNodeItem>
 
4
  import { ClapEntity, ClapSegment, ClapWorkflow } from '@aitube/clap'
5
  import { TreeNodeType } from '../core/tree/types'
6
 
7
+ export type DefaultTreeNodeList = 'DEFAULT_TREE_NODE_LIST'
8
+ export type DefaultTreeNodeItem = 'DEFAULT_TREE_NODE_ITEM'
9
+ export type DefaultTreeNodeEmpty = 'DEFAULT_TREE_NODE_EMPTY'
10
+
11
+ export type DefaultTreeNode =
12
+ | DefaultTreeNodeList
13
+ | DefaultTreeNodeItem
14
+ | DefaultTreeNodeEmpty
15
+
16
+ // ------------------------------------------
17
+ export type CommunityTreeNodeDataset = 'COMMUNITY_TREE_NODE_LIST_DATASET'
18
+ export type CommunityTreeNodeList = 'COMMUNITY_TREE_NODE_LIST_FOLDER'
19
+ export type CommunityTreeNodeItem = 'COMMUNITY_TREE_NODE_ITEM_FILE'
20
+
21
+ export type CommunityTreeNode =
22
+ | CommunityTreeNodeDataset
23
+ | CommunityTreeNodeList
24
+ | CommunityTreeNodeItem
25
+
26
+ // ------------------------------------------
27
+
28
+ export type DeviceTreeNodeList = 'DEVICE_TREE_NODE_LIST_FOLDER'
29
+ export type DeviceTreeNodeItem = 'DEVICE_TREE_NODE_ITEM_FILE'
30
+
31
+ export type DeviceTreeNode = DeviceTreeNodeList | DeviceTreeNodeItem
32
+
33
+ // ------------------------------------------
34
+
35
+ export type FlowTreeNodeList = 'FLOW_TREE_NODE_LIST_WORKFLOWS'
36
+ export type FlowTreeNodeItem = 'FLOW_TREE_NODE_ITEM_WORKFLOW'
37
+
38
+ export type FlowTreeNode = FlowTreeNodeList | FlowTreeNodeItem
39
+
40
+ // ------------------------------------------
41
+
42
+ export type EntityTreeNodeList = 'ENTITY_TREE_NODE_LIST_ENTITIES'
43
+ export type EntityTreeNodeItem = 'ENTITY_TREE_NODE_ITEM_ENTITY'
44
+
45
+ export type EntityTreeNode = EntityTreeNodeList | EntityTreeNodeItem
46
+
47
+ // ------------------------------------------
48
+
49
+ // some specialized types
50
+
51
+ // TODO
52
+
53
+ // ------------------------------------------
54
+
55
+ // Root elements
56
+ export type TreeRootProject = 'TREE_ROOT_PROJECT'
57
+ export type TreeRootDevice = 'TREE_ROOT_DEVICE'
58
+ export type TreeRootTeam = 'TREE_ROOT_TEAM'
59
+ export type TreeRootCommunity = 'TREE_ROOT_COMMUNITY'
60
+ export type TreeRootBuiltin = 'TREE_ROOT_BUILTIN'
61
+
62
+ export type TreeRoot =
63
+ | TreeRootProject
64
+ | TreeRootDevice
65
+ | TreeRootTeam
66
+ | TreeRootCommunity
67
+ | TreeRootBuiltin
68
+
69
+ // ------------------------------------------
70
 
71
  // can be a file or folder
72
+ export type DeviceFileOrFolder = {
73
  id: string // can be the path for now
74
  path: string // path to the file content
75
  fileName: string
 
78
  extension: string
79
  mimetype: string
80
  isDirectory: boolean
81
+ items: DeviceFileOrFolder[]
82
  }
83
 
84
+ export type DeviceCollection = {
85
  id: string
86
  name: string
87
  description: string
88
  path: string // path to the root directory (eg "~/Clapper")
89
+ items: DeviceFileOrFolder[] // files or folders
90
  }
91
 
92
  // can be a file or folder
93
+ export type CommunityFileOrFolder = {
94
  id: string // can be the path for now (so not a uuid)
95
  assetUrl: string // full URL to download the content (eg. to use with wget)
96
  datasetName: string
 
104
  mimeType: string
105
  isDirectory: boolean
106
  isPrivate: boolean
107
+ items: CommunityFileOrFolder[]
108
  }
109
 
110
+ export type CommunityEntityCollection = {
111
+ id: string // huggingface id (not a uuid)
112
  name: string
113
  description: string
114
  userName: string
115
  datasetName: string
116
  repository: string
117
  url: string
118
+ items: ClapEntity[]
119
+ }
120
+
121
+ export type ProjectEntityCollection = {
122
+ id: string
123
+ name: string
124
+ description: string
125
+ items: ClapEntity[]
126
  }
127
 
128
  export type ReplicateModel = {
 
170
  }
171
 
172
  // TODO unify this a bit, at least in the naming scheme
173
+ export type LibraryNodeFileItem = DeviceFileOrFolder | CommunityFileOrFolder
174
+
175
+ // ------------------------------------------
176
+
177
+ export type LibraryNodeType =
178
+ | DefaultTreeNode
179
+ | CommunityTreeNode
180
+ | DeviceTreeNode
181
+ | FlowTreeNode
182
+ | EntityTreeNode
183
+ | TreeRoot
184
 
185
  // TODO unify this a bit, at least in the naming scheme
186
+ export type TreeNodeItem =
187
  | ClapEntity
 
 
 
 
 
 
 
 
 
188
  | ClapSegment
189
+ | ScreenplaySequence
190
  | ClapWorkflow
191
+ | WorkflowCollection
192
+
193
+ // TODO: if we keep them, then rename those to things like
194
+ // ReplicateWorkflowCollection,
195
+ // CivitaiWorkflowCollection etc
196
+ // | ReplicateCollection
197
+ // | ReplicateModel
198
+ // | CivitaiCollection
199
+ // | CivitaiModel
200
+ | DeviceCollection
201
+ | DeviceFileOrFolder
202
+ | CommunityEntityCollection
203
+ | CommunityFileOrFolder
204
 
205
  // a model library is a collection of models
206
  // this collection can itself include sub-models
207
+ export type LibraryTreeNode = TreeNodeType<LibraryNodeType, TreeNodeItem>
src/components/tree-browsers/utils/isSomething.ts CHANGED
@@ -6,63 +6,51 @@
6
 
7
  import { ClapEntity } from '@aitube/clap'
8
  import {
9
- HuggingFaceUserCollection,
10
- HuggingFaceUserItem,
11
- LibraryNodeItem,
12
  LibraryNodeType,
13
- LocalUserCollection,
14
- LocalUserItem,
15
- ReplicateCollection,
16
  } from '../types'
17
 
18
- export const isLocalUserCollection = (
19
  nodeType: LibraryNodeType,
20
- data: LibraryNodeItem
21
- ): data is LocalUserCollection => {
22
- return nodeType === 'LIB_NODE_LOCAL_USER_COLLECTION'
23
  }
24
 
25
- export const isLocalUserItem = (
26
  nodeType: LibraryNodeType,
27
- data: LibraryNodeItem
28
- ): data is LocalUserItem => {
29
  return (
30
- nodeType === 'LIB_NODE_LOCAL_USER_FILE' ||
31
- nodeType === 'LIB_NODE_LOCAL_USER_FOLDER'
32
  )
33
  }
34
 
35
- export const isHuggingFaceUserCollection = (
36
  nodeType: LibraryNodeType,
37
- data: LibraryNodeItem
38
- ): data is HuggingFaceUserCollection => {
39
- return nodeType === 'LIB_NODE_HUGGINGFACE_USER_COLLECTION'
40
  }
41
 
42
- export const isHuggingFaceUserItem = (
43
  nodeType: LibraryNodeType,
44
- data: LibraryNodeItem
45
- ): data is HuggingFaceUserItem => {
46
  return (
47
- nodeType === 'LIB_NODE_HUGGINGFACE_USER_FILE' ||
48
- nodeType === 'LIB_NODE_HUGGINGFACE_USER_FOLDER'
49
  )
50
  }
51
 
52
- export const isReplicateCollection = (
53
- nodeType: LibraryNodeType,
54
- data: LibraryNodeItem
55
- ): data is ReplicateCollection => {
56
- return nodeType === 'LIB_NODE_REPLICATE_COLLECTION'
57
- }
58
-
59
  export const isClapEntity = (
60
  nodeType: LibraryNodeType,
61
- data: LibraryNodeItem
62
  ): data is ClapEntity => {
63
- return (
64
- nodeType === 'LIB_NODE_REPLICATE_MODEL' ||
65
- nodeType === 'LIB_NODE_HUGGINGFACE_MODEL' ||
66
- nodeType === 'LIB_NODE_GENERIC_MODEL'
67
- )
68
  }
 
6
 
7
  import { ClapEntity } from '@aitube/clap'
8
  import {
9
+ CommunityEntityCollection,
10
+ CommunityFileOrFolder,
11
+ TreeNodeItem,
12
  LibraryNodeType,
13
+ DeviceCollection,
14
+ DeviceFileOrFolder,
 
15
  } from '../types'
16
 
17
+ export const isFSCollection = (
18
  nodeType: LibraryNodeType,
19
+ data: TreeNodeItem
20
+ ): data is DeviceCollection => {
21
+ return nodeType === 'TREE_ROOT_DEVICE'
22
  }
23
 
24
+ export const isFSFileOrFolder = (
25
  nodeType: LibraryNodeType,
26
+ data: TreeNodeItem
27
+ ): data is DeviceFileOrFolder => {
28
  return (
29
+ nodeType === 'DEVICE_TREE_NODE_LIST_FOLDER' ||
30
+ nodeType === 'DEVICE_TREE_NODE_ITEM_FILE'
31
  )
32
  }
33
 
34
+ export const isCommunityEntityCollection = (
35
  nodeType: LibraryNodeType,
36
+ data: TreeNodeItem
37
+ ): data is CommunityEntityCollection => {
38
+ return nodeType === 'COMMUNITY_TREE_NODE_LIST_DATASET'
39
  }
40
 
41
+ export const isCommunityFileOrFolder = (
42
  nodeType: LibraryNodeType,
43
+ data: TreeNodeItem
44
+ ): data is CommunityFileOrFolder => {
45
  return (
46
+ nodeType === 'COMMUNITY_TREE_NODE_LIST_FOLDER' ||
47
+ nodeType === 'COMMUNITY_TREE_NODE_ITEM_FILE'
48
  )
49
  }
50
 
 
 
 
 
 
 
 
51
  export const isClapEntity = (
52
  nodeType: LibraryNodeType,
53
+ data: TreeNodeItem
54
  ): data is ClapEntity => {
55
+ return nodeType === 'ENTITY_TREE_NODE_ITEM_ENTITY'
 
 
 
 
56
  }
src/components/ui/menubar.tsx CHANGED
@@ -23,7 +23,7 @@ const Menubar = React.forwardRef<
23
  <MenubarPrimitive.Root
24
  ref={ref}
25
  className={cn(
26
- 'flex h-full items-center space-x-0 rounded-md border border-none border-stone-200 bg-white p-1 shadow-none dark:border-none dark:border-stone-900 dark:bg-stone-900',
27
  className
28
  )}
29
  {...props}
@@ -38,7 +38,7 @@ const MenubarTrigger = React.forwardRef<
38
  <MenubarPrimitive.Trigger
39
  ref={ref}
40
  className={cn(
41
- 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-normal outline-none data-[state=open]:bg-stone-100 data-[state=open]:text-stone-900 focus:bg-stone-100 focus:text-stone-900 dark:text-stone-400 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-300 dark:focus:bg-stone-800 dark:focus:text-stone-300',
42
  className
43
  )}
44
  {...props}
@@ -55,7 +55,7 @@ const MenubarSubTrigger = React.forwardRef<
55
  <MenubarPrimitive.SubTrigger
56
  ref={ref}
57
  className={cn(
58
- 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-stone-100 data-[state=open]:text-stone-900 focus:bg-stone-100 focus:text-stone-900 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-200 dark:focus:bg-stone-800 dark:focus:text-stone-200',
59
  inset && 'pl-8',
60
  className
61
  )}
@@ -74,7 +74,7 @@ const MenubarSubContent = React.forwardRef<
74
  <MenubarPrimitive.SubContent
75
  ref={ref}
76
  className={cn(
77
- 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-900 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-stone-800 dark:bg-stone-900 dark:text-stone-200',
78
  className
79
  )}
80
  {...props}
@@ -97,7 +97,7 @@ const MenubarContent = React.forwardRef<
97
  alignOffset={alignOffset}
98
  sideOffset={sideOffset}
99
  className={cn(
100
- 'z-50 min-w-[12rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-900 shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-stone-800 dark:bg-stone-900 dark:text-stone-200',
101
  className
102
  )}
103
  {...props}
@@ -116,7 +116,7 @@ const MenubarItem = React.forwardRef<
116
  <MenubarPrimitive.Item
117
  ref={ref}
118
  className={cn(
119
- 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-stone-100 focus:text-stone-900 dark:focus:bg-stone-800 dark:focus:text-stone-200',
120
  inset && 'pl-8',
121
  className
122
  )}
@@ -132,7 +132,7 @@ const MenubarCheckboxItem = React.forwardRef<
132
  <MenubarPrimitive.CheckboxItem
133
  ref={ref}
134
  className={cn(
135
- 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-stone-100 focus:text-stone-900 dark:focus:bg-stone-800 dark:focus:text-stone-200',
136
  className
137
  )}
138
  checked={checked}
@@ -155,7 +155,7 @@ const MenubarRadioItem = React.forwardRef<
155
  <MenubarPrimitive.RadioItem
156
  ref={ref}
157
  className={cn(
158
- 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-stone-100 focus:text-stone-900 dark:focus:bg-stone-800 dark:focus:text-stone-200',
159
  className
160
  )}
161
  {...props}
@@ -194,7 +194,10 @@ const MenubarSeparator = React.forwardRef<
194
  >(({ className, ...props }, ref) => (
195
  <MenubarPrimitive.Separator
196
  ref={ref}
197
- className={cn('-mx-1 my-1 h-px bg-stone-100 dark:bg-stone-800', className)}
 
 
 
198
  {...props}
199
  />
200
  ))
@@ -207,7 +210,7 @@ const MenubarShortcut = ({
207
  return (
208
  <span
209
  className={cn(
210
- 'text-stone-2000 ml-auto text-xs tracking-widest dark:text-stone-400',
211
  className
212
  )}
213
  {...props}
 
23
  <MenubarPrimitive.Root
24
  ref={ref}
25
  className={cn(
26
+ 'flex h-full items-center space-x-0 border border-none border-neutral-100/50 bg-white/50 p-1 shadow-none dark:border-none dark:border-neutral-950/50 dark:bg-neutral-950/50',
27
  className
28
  )}
29
  {...props}
 
38
  <MenubarPrimitive.Trigger
39
  ref={ref}
40
  className={cn(
41
+ 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-normal outline-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 focus:bg-neutral-100 focus:text-neutral-900 dark:text-neutral-400 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-300 dark:focus:bg-neutral-800 dark:focus:text-neutral-300',
42
  className
43
  )}
44
  {...props}
 
55
  <MenubarPrimitive.SubTrigger
56
  ref={ref}
57
  className={cn(
58
+ 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 focus:bg-neutral-100 focus:text-neutral-900 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-200 dark:focus:bg-neutral-800 dark:focus:text-neutral-200',
59
  inset && 'pl-8',
60
  className
61
  )}
 
74
  <MenubarPrimitive.SubContent
75
  ref={ref}
76
  className={cn(
77
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-900 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-200',
78
  className
79
  )}
80
  {...props}
 
97
  alignOffset={alignOffset}
98
  sideOffset={sideOffset}
99
  className={cn(
100
+ 'z-50 min-w-[12rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-900 shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-200',
101
  className
102
  )}
103
  {...props}
 
116
  <MenubarPrimitive.Item
117
  ref={ref}
118
  className={cn(
119
+ 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-200',
120
  inset && 'pl-8',
121
  className
122
  )}
 
132
  <MenubarPrimitive.CheckboxItem
133
  ref={ref}
134
  className={cn(
135
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-200',
136
  className
137
  )}
138
  checked={checked}
 
155
  <MenubarPrimitive.RadioItem
156
  ref={ref}
157
  className={cn(
158
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-45 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-200',
159
  className
160
  )}
161
  {...props}
 
194
  >(({ className, ...props }, ref) => (
195
  <MenubarPrimitive.Separator
196
  ref={ref}
197
+ className={cn(
198
+ '-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800',
199
+ className
200
+ )}
201
  {...props}
202
  />
203
  ))
 
210
  return (
211
  <span
212
  className={cn(
213
+ 'text-neutral-2000 ml-auto text-xs tracking-widest dark:text-neutral-400',
214
  className
215
  )}
216
  {...props}
src/services/editors/script-editor/useScriptEditor.ts CHANGED
@@ -48,11 +48,21 @@ export const useScriptEditor = create<ScriptEditorStore>((set, get) => ({
48
  if (!textModel) {
49
  return
50
  }
51
- // we need to update the model
52
- textModel?.setValue(draft)
 
 
 
 
 
 
 
 
53
 
54
  // and highlight the text again
55
- highlightElements()
 
 
56
  },
57
  publishDraftToTimeline: async (): Promise<void> => {
58
  const { draft } = get()
 
48
  if (!textModel) {
49
  return
50
  }
51
+
52
+ try {
53
+ // we need to update the model
54
+ textModel?.setValue(draft)
55
+ } catch (err) {
56
+ // to catch the "Error: Model is disposed!"
57
+ // this can happen if the timing isn't right,
58
+ // the model might already (or still) be disposed of
59
+ return
60
+ }
61
 
62
  // and highlight the text again
63
+ try {
64
+ highlightElements()
65
+ } catch (err) {}
66
  },
67
  publishDraftToTimeline: async (): Promise<void> => {
68
  const { draft } = get()