diff --git a/web/app/components/workflow/skill/file-item.tsx b/web/app/components/workflow/skill/file-item.tsx index 198cc8e0e4..be34c326db 100644 --- a/web/app/components/workflow/skill/file-item.tsx +++ b/web/app/components/workflow/skill/file-item.tsx @@ -1,12 +1,46 @@ -import type { FC } from 'react' +import type { FC, ReactNode } from 'react' import * as React from 'react' +import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon' +import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' +import { cn } from '@/utils/classnames' + +type FileItemProps = { + name: string + prefix?: ReactNode + active?: boolean +} + +const getAppearanceType = (name: string) => { + const extension = name.split('.').pop()?.toLowerCase() ?? '' + + if (['md', 'markdown', 'mdx'].includes(extension)) + return FileAppearanceTypeEnum.markdown + + if (['json', 'yaml', 'yml', 'toml', 'js', 'jsx', 'ts', 'tsx', 'py', 'schema'].includes(extension)) + return FileAppearanceTypeEnum.code + + return FileAppearanceTypeEnum.document +} + +const FileItem: FC = ({ name, prefix, active = false }) => { + const appearanceType = getAppearanceType(name) -const FileItem: FC = () => { return (
+ > + {prefix} +
+ + + {name} + +
+
) } diff --git a/web/app/components/workflow/skill/files.tsx b/web/app/components/workflow/skill/files.tsx index c9a53dd88e..4c428a2adb 100644 --- a/web/app/components/workflow/skill/files.tsx +++ b/web/app/components/workflow/skill/files.tsx @@ -1,15 +1,97 @@ -import type { FC, PropsWithChildren } from 'react' +'use client' +import type { FC, ReactNode } from 'react' +import type { ParentId, ResourceItem, ResourceItemList } from './type' +import { RiDragDropLine } from '@remixicon/react' import * as React from 'react' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import FileItem from './file-item' +import FoldItem from './fold-item' +import { ResourceKind, SKILL_ROOT_ID } from './type' -type FilesProps = PropsWithChildren +const TreeIndent = ({ depth }: { depth: number }) => { + if (depth <= 0) + return null -const Files: FC = ({ children }) => { return ( -
- {children} +
+ {Array.from({ length: depth }).map((_, index) => ( + + + + ))} +
+ ) +} + +type FilesProps = { + items: ResourceItemList + activeItemId?: string +} + +const buildChildrenMap = (items: ResourceItemList) => { + const map = new Map() + items.forEach((item) => { + const parentId = item.parent_id ?? null + const existing = map.get(parentId) + if (existing) + existing.push(item) + else + map.set(parentId, [item]) + }) + return map +} + +const Files: FC = ({ items, activeItemId }) => { + const { t } = useTranslation() + const childrenMap = useMemo(() => buildChildrenMap(items), [items]) + + const renderNodes = (parentId: ParentId, depth: number): ReactNode[] => { + const children = childrenMap.get(parentId) || [] + + return children.flatMap((item) => { + const prefix = + const isActive = item.id === activeItemId + const nodes: ReactNode[] = [] + + if (item.kind === ResourceKind.folder) { + nodes.push( + , + ) + nodes.push(...renderNodes(item.id, depth + 1)) + } + else { + nodes.push( + , + ) + } + + return nodes + }) + } + + return ( +
+
+ {renderNodes(SKILL_ROOT_ID, 0)} +
+
+ + + {t('skillSidebar.dropTip', { ns: 'workflow' })} + +
) } diff --git a/web/app/components/workflow/skill/fold-item.tsx b/web/app/components/workflow/skill/fold-item.tsx index 670199c9ca..93f977a608 100644 --- a/web/app/components/workflow/skill/fold-item.tsx +++ b/web/app/components/workflow/skill/fold-item.tsx @@ -1,12 +1,40 @@ -import type { FC } from 'react' +import type { FC, ReactNode } from 'react' +import { RiFolder6Line, RiFolderOpenLine } from '@remixicon/react' import * as React from 'react' +import { cn } from '@/utils/classnames' + +type FoldItemProps = { + name: string + prefix?: ReactNode + active?: boolean + open?: boolean +} + +const FoldItem: FC = ({ name, prefix, active = false, open = false }) => { + const Icon = open ? RiFolderOpenLine : RiFolder6Line -const FoldItem: FC = () => { return (
+ > + {prefix} +
+ + + {name} + +
+
) } diff --git a/web/app/components/workflow/skill/main.tsx b/web/app/components/workflow/skill/main.tsx index d560ab5dfb..179b9c4d9c 100644 --- a/web/app/components/workflow/skill/main.tsx +++ b/web/app/components/workflow/skill/main.tsx @@ -5,25 +5,22 @@ import EditorArea from './editor-area' import EditorBody from './editor-body' import EditorTabItem from './editor-tab-item' import EditorTabs from './editor-tabs' -import FileItem from './file-item' import Files from './files' -import FoldItem from './fold-item' +import { mockSkillItems } from './mock-data' import Sidebar from './sidebar' import SidebarSearchAdd from './sidebar-search-add' import SkillDocEditor from './skill-doc-editor' import SkillPageLayout from './skill-page-layout' const SkillMain: FC = () => { + const activeItemId = 'skills/_schemas/email-writer/output-schema' + return (
- - - - - + diff --git a/web/app/components/workflow/skill/sidebar-search-add.tsx b/web/app/components/workflow/skill/sidebar-search-add.tsx index 2fc0c1b10e..0e139c0cac 100644 --- a/web/app/components/workflow/skill/sidebar-search-add.tsx +++ b/web/app/components/workflow/skill/sidebar-search-add.tsx @@ -1,14 +1,35 @@ +'use client' import type { FC } from 'react' +import { RiAddLine } from '@remixicon/react' import * as React from 'react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import SearchInput from '@/app/components/base/search-input' +import { cn } from '@/utils/classnames' const SidebarSearchAdd: FC = () => { + const [value, setValue] = useState('') + const { t } = useTranslation() + return (
-
-
+ +
) } diff --git a/web/app/components/workflow/skill/sidebar.tsx b/web/app/components/workflow/skill/sidebar.tsx index 312c150026..03f86f757a 100644 --- a/web/app/components/workflow/skill/sidebar.tsx +++ b/web/app/components/workflow/skill/sidebar.tsx @@ -6,7 +6,7 @@ type SidebarProps = PropsWithChildren const Sidebar: FC = ({ children }) => { return (