Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
β’
3420261
1
Parent(s):
c1021fe
add minimalist tree system
Browse files- src/components/{tree-browsers/project-tree-browser β editors/EntityEditor/EntityTree}/index.tsx +23 -20
- src/components/{tree-browsers/stores/useProjectLibrary.ts β editors/EntityEditor/EntityTree/useEntityTree.ts} +103 -36
- src/components/editors/EntityEditor/EntityViewer/EntityList.tsx +65 -0
- src/components/editors/EntityEditor/EntityViewer/index.tsx +192 -0
- src/components/editors/EntityEditor/index.tsx +12 -238
- src/components/{tree-browsers/workflow-tree-browser β editors/WorkflowEditor/WorkflowTree}/index.tsx +23 -26
- src/components/{tree-browsers/stores/useWorkflowLibrary.ts β editors/WorkflowEditor/WorkflowTree/useWorkflowTree.ts} +29 -21
- src/components/editors/WorkflowEditor/{viewer β WorkflowViewer/ReactFlowCanvas}/NodeView.tsx +1 -1
- src/components/editors/WorkflowEditor/{clapWorkflowToReactWorkflow.ts β WorkflowViewer/ReactFlowCanvas/clapWorkflowToReactWorkflow.ts} +1 -1
- src/components/editors/WorkflowEditor/{specialized β WorkflowViewer/ReactFlowCanvas/formats}/comfyui/types.ts +0 -0
- src/components/editors/WorkflowEditor/{specialized β WorkflowViewer/ReactFlowCanvas/formats}/falai/types.ts +0 -0
- src/components/editors/WorkflowEditor/{specialized β WorkflowViewer/ReactFlowCanvas/formats}/glif/glifToReactWorkflow.ts +0 -0
- src/components/editors/WorkflowEditor/{specialized β WorkflowViewer/ReactFlowCanvas/formats}/glif/types.ts +0 -0
- src/components/editors/WorkflowEditor/{viewer/WorkflowView.tsx β WorkflowViewer/ReactFlowCanvas/index.tsx} +24 -5
- src/components/editors/WorkflowEditor/{samples β WorkflowViewer/ReactFlowCanvas/samples}/glif.ts +1 -1
- src/components/editors/WorkflowEditor/{types.ts β WorkflowViewer/ReactFlowCanvas/types.ts} +0 -0
- src/components/editors/WorkflowEditor/WorkflowViewer/index.tsx +31 -0
- src/components/editors/WorkflowEditor/index.tsx +12 -32
- src/components/editors/WorkflowEditor/viewer/README.md +0 -1
- src/components/forms/index.ts +9 -0
- src/components/toolbars/top-bar/index.tsx +18 -11
- src/components/toolbars/top-menu/index.tsx +1 -1
- src/components/tree-browsers/model-tree-browser/index.tsx +0 -88
- src/components/tree-browsers/model-tree-browser/tree-item-viewer.tsx +0 -17
- src/components/tree-browsers/project-tree-browser/tree-item-viewer.tsx +0 -17
- src/components/tree-browsers/stores/useCivitaiCollections.ts +0 -24
- src/components/tree-browsers/stores/useEntityLibrary.ts +0 -336
- src/components/tree-browsers/stores/useFileLibrary.txt +0 -204
- src/components/tree-browsers/stores/useReplicateCollections.ts +0 -24
- src/components/tree-browsers/{stores β style}/treeNodeStyles.ts +5 -3
- src/components/tree-browsers/types.ts +106 -80
- src/components/tree-browsers/utils/isSomething.ts +25 -37
- src/components/ui/menubar.tsx +13 -10
- 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 '
|
5 |
-
import {
|
6 |
-
import { LibraryNodeItem, LibraryNodeType } from '../types'
|
7 |
import { Tree } from '@/components/core/tree'
|
8 |
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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?:
|
27 |
) => {
|
28 |
console.log(`calling selectTreeNodeById(id)`)
|
29 |
selectTreeNode(id, nodeType, nodeItem)
|
@@ -44,17 +49,15 @@ export function ProjectTreeBrowser() {
|
|
44 |
}
|
45 |
|
46 |
return (
|
47 |
-
<
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
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,
|
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 '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
export const useProjectLibrary = create<{
|
16 |
libraryTreeRoot: LibraryTreeNode[]
|
17 |
init: () => void
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
selectedNodeType?: LibraryNodeType
|
21 |
-
selectEntity: (entity?: ClapEntity) => void
|
22 |
selectTreeNode: (
|
23 |
treeNodeId?: string | null,
|
24 |
nodeType?: LibraryNodeType,
|
25 |
-
nodeItem?:
|
26 |
) => void
|
27 |
selectedTreeNodeId: string | null
|
28 |
}>((set, get) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
libraryTreeRoot: [],
|
30 |
init: () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
set({
|
32 |
-
|
|
|
|
|
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:
|
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:
|
60 |
-
isExpanded:
|
61 |
children: [],
|
62 |
}
|
63 |
|
@@ -67,49 +139,43 @@ export const useProjectLibrary = create<{
|
|
67 |
data: undefined,
|
68 |
label: 'Misc',
|
69 |
icon: icons.misc,
|
70 |
-
className:
|
71 |
-
isExpanded:
|
72 |
children: [],
|
73 |
}
|
74 |
|
75 |
entities.forEach((entity) => {
|
76 |
const node: LibraryTreeNode = {
|
77 |
-
nodeType:
|
78 |
id: entity.id,
|
79 |
data: entity,
|
80 |
label: entity.label,
|
81 |
icon: icons.misc,
|
82 |
-
className:
|
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 |
-
|
98 |
-
|
99 |
-
|
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
|
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?:
|
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 |
-
|
|
|
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 {
|
2 |
-
import {
|
3 |
-
import {
|
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 |
-
<
|
154 |
-
{
|
155 |
-
<
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
<
|
160 |
-
|
161 |
-
|
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 {
|
7 |
-
import {
|
8 |
import { Tree } from '@/components/core/tree'
|
9 |
|
10 |
-
import {
|
11 |
|
12 |
-
export function
|
13 |
-
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
|
|
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?:
|
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 |
-
<
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
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 |
-
|
6 |
import { icons } from '@/components/icons'
|
7 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
-
export const
|
10 |
-
|
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?:
|
33 |
selectedNodeType?: LibraryNodeType
|
34 |
selectTreeNode: (
|
35 |
treeNodeId?: string | null,
|
36 |
nodeType?: LibraryNodeType,
|
37 |
-
nodeItem?:
|
38 |
) => void
|
39 |
selectedTreeNodeId: string | null
|
40 |
}>((set, get) => ({
|
41 |
-
|
42 |
communityLibraryTreeNodeId: '',
|
43 |
libraryTreeRoot: [],
|
44 |
init: () => {
|
45 |
-
const
|
46 |
id: UUID(),
|
47 |
-
nodeType: '
|
48 |
-
label: '
|
49 |
-
icon: icons.
|
50 |
className: libraryClassName,
|
51 |
isExpanded: true,
|
52 |
children: [
|
53 |
{
|
54 |
id: UUID(),
|
55 |
-
nodeType: '
|
56 |
-
label: '
|
57 |
-
icon: icons.
|
58 |
className: collectionClassName,
|
59 |
},
|
60 |
],
|
@@ -62,25 +70,25 @@ export const useWorkflowLibrary = create<{
|
|
62 |
|
63 |
const communityLibrary: LibraryTreeNode = {
|
64 |
id: UUID(),
|
65 |
-
nodeType: '
|
66 |
label: 'Community workflows',
|
67 |
icon: icons.community,
|
68 |
className: libraryClassName,
|
69 |
children: [
|
70 |
{
|
71 |
id: UUID(),
|
72 |
-
nodeType: '
|
73 |
-
label: '
|
74 |
icon: icons.community,
|
75 |
className: collectionClassName,
|
76 |
},
|
77 |
],
|
78 |
}
|
79 |
|
80 |
-
const libraryTreeRoot = [
|
81 |
|
82 |
set({
|
83 |
-
|
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?:
|
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 |
-
|
|
|
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 './
|
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 '
|
18 |
import { useWorkflowEditor } from '@/services/editors'
|
19 |
|
20 |
-
import { glifs } from '
|
21 |
-
import { glifToReactWorkflow } from '
|
22 |
import { useTheme } from '@/services'
|
23 |
|
24 |
const nodeTypes = {
|
25 |
custom: NodeView,
|
26 |
}
|
27 |
|
28 |
-
export function
|
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
|
|
|
|
|
|
|
|
|
|
|
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 '../
|
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 {
|
2 |
-
|
3 |
-
import {
|
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 |
-
<
|
31 |
-
<
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
|
|
|
|
|
|
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 |
-
|
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
|
10 |
|
11 |
return (
|
12 |
<div
|
13 |
-
className={cn(
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
`border-b`,
|
18 |
-
`border-b-stone-700`
|
19 |
-
)}
|
20 |
>
|
21 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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="
|
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 =
|
|
|
4 |
|
5 |
-
export const collectionClassName =
|
|
|
6 |
|
7 |
export const itemClassName =
|
8 |
-
'text-
|
|
|
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 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
export type
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
24 |
-
|
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
export type
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
export type
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
// can be a file or folder
|
67 |
-
export type
|
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:
|
77 |
}
|
78 |
|
79 |
-
export type
|
80 |
id: string
|
81 |
name: string
|
82 |
description: string
|
83 |
path: string // path to the root directory (eg "~/Clapper")
|
84 |
-
items:
|
85 |
}
|
86 |
|
87 |
// can be a file or folder
|
88 |
-
export type
|
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:
|
103 |
}
|
104 |
|
105 |
-
export type
|
106 |
-
id: string // huggingface
|
107 |
name: string
|
108 |
description: string
|
109 |
userName: string
|
110 |
datasetName: string
|
111 |
repository: string
|
112 |
url: string
|
113 |
-
items:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
// TODO unify this a bit, at least in the naming scheme
|
164 |
-
export type
|
165 |
| ClapEntity
|
166 |
-
| ReplicateCollection
|
167 |
-
| ReplicateModel
|
168 |
-
| CivitaiCollection
|
169 |
-
| CivitaiModel
|
170 |
-
| LocalUserCollection
|
171 |
-
| LocalUserItem
|
172 |
-
| HuggingFaceUserCollection
|
173 |
-
| HuggingFaceUserItem
|
174 |
-
| ScreenplaySequence
|
175 |
| ClapSegment
|
176 |
-
|
|
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,
|
|
|
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 |
-
|
10 |
-
|
11 |
-
|
12 |
LibraryNodeType,
|
13 |
-
|
14 |
-
|
15 |
-
ReplicateCollection,
|
16 |
} from '../types'
|
17 |
|
18 |
-
export const
|
19 |
nodeType: LibraryNodeType,
|
20 |
-
data:
|
21 |
-
): data is
|
22 |
-
return nodeType === '
|
23 |
}
|
24 |
|
25 |
-
export const
|
26 |
nodeType: LibraryNodeType,
|
27 |
-
data:
|
28 |
-
): data is
|
29 |
return (
|
30 |
-
nodeType === '
|
31 |
-
nodeType === '
|
32 |
)
|
33 |
}
|
34 |
|
35 |
-
export const
|
36 |
nodeType: LibraryNodeType,
|
37 |
-
data:
|
38 |
-
): data is
|
39 |
-
return nodeType === '
|
40 |
}
|
41 |
|
42 |
-
export const
|
43 |
nodeType: LibraryNodeType,
|
44 |
-
data:
|
45 |
-
): data is
|
46 |
return (
|
47 |
-
nodeType === '
|
48 |
-
nodeType === '
|
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:
|
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
|
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-
|
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-
|
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-
|
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-
|
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-
|
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-
|
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-
|
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(
|
|
|
|
|
|
|
198 |
{...props}
|
199 |
/>
|
200 |
))
|
@@ -207,7 +210,7 @@ const MenubarShortcut = ({
|
|
207 |
return (
|
208 |
<span
|
209 |
className={cn(
|
210 |
-
'text-
|
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 |
-
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
// and highlight the text again
|
55 |
-
|
|
|
|
|
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()
|