| @@ -0,0 +1,7 @@ | |||
| <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path d="M28.0049 16.5C28.0049 20.9183 24.4231 24.5 20.0049 24.5C15.5866 24.5 12.0049 20.9183 12.0049 16.5C12.0049 12.0817 15.5866 8.5 20.0049 8.5C24.4231 8.5 28.0049 12.0817 28.0049 16.5Z" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M4.00488 16.5H6.67155" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M4.00488 9.83301H8.00488" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M4.00488 23.167H8.00488" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M26 22.5L29.3333 25.8333" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |||
| </svg> | |||
| @@ -0,0 +1,77 @@ | |||
| { | |||
| "icon": { | |||
| "type": "element", | |||
| "isRootNode": true, | |||
| "name": "svg", | |||
| "attributes": { | |||
| "width": "32", | |||
| "height": "33", | |||
| "viewBox": "0 0 32 33", | |||
| "fill": "none", | |||
| "xmlns": "http://www.w3.org/2000/svg" | |||
| }, | |||
| "children": [ | |||
| { | |||
| "type": "element", | |||
| "name": "path", | |||
| "attributes": { | |||
| "d": "M28.0049 16.5C28.0049 20.9183 24.4231 24.5 20.0049 24.5C15.5866 24.5 12.0049 20.9183 12.0049 16.5C12.0049 12.0817 15.5866 8.5 20.0049 8.5C24.4231 8.5 28.0049 12.0817 28.0049 16.5Z", | |||
| "stroke": "currentColor", | |||
| "stroke-width": "2", | |||
| "stroke-linecap": "round", | |||
| "stroke-linejoin": "round" | |||
| }, | |||
| "children": [] | |||
| }, | |||
| { | |||
| "type": "element", | |||
| "name": "path", | |||
| "attributes": { | |||
| "d": "M4.00488 16.5H6.67155", | |||
| "stroke": "currentColor", | |||
| "stroke-width": "2", | |||
| "stroke-linecap": "round", | |||
| "stroke-linejoin": "round" | |||
| }, | |||
| "children": [] | |||
| }, | |||
| { | |||
| "type": "element", | |||
| "name": "path", | |||
| "attributes": { | |||
| "d": "M4.00488 9.83301H8.00488", | |||
| "stroke": "currentColor", | |||
| "stroke-width": "2", | |||
| "stroke-linecap": "round", | |||
| "stroke-linejoin": "round" | |||
| }, | |||
| "children": [] | |||
| }, | |||
| { | |||
| "type": "element", | |||
| "name": "path", | |||
| "attributes": { | |||
| "d": "M4.00488 23.167H8.00488", | |||
| "stroke": "currentColor", | |||
| "stroke-width": "2", | |||
| "stroke-linecap": "round", | |||
| "stroke-linejoin": "round" | |||
| }, | |||
| "children": [] | |||
| }, | |||
| { | |||
| "type": "element", | |||
| "name": "path", | |||
| "attributes": { | |||
| "d": "M26 22.5L29.3333 25.8333", | |||
| "stroke": "currentColor", | |||
| "stroke-width": "2", | |||
| "stroke-linecap": "round", | |||
| "stroke-linejoin": "round" | |||
| }, | |||
| "children": [] | |||
| } | |||
| ] | |||
| }, | |||
| "name": "SearchMenu" | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| // GENERATE BY script | |||
| // DON NOT EDIT IT MANUALLY | |||
| import * as React from 'react' | |||
| import data from './SearchMenu.json' | |||
| import IconBase from '@/app/components/base/icons/IconBase' | |||
| import type { IconData } from '@/app/components/base/icons/IconBase' | |||
| const Icon = ( | |||
| { | |||
| ref, | |||
| ...props | |||
| }: React.SVGProps<SVGSVGElement> & { | |||
| ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>; | |||
| }, | |||
| ) => <IconBase {...props} ref={ref} data={data as IconData} /> | |||
| Icon.displayName = 'SearchMenu' | |||
| export default Icon | |||
| @@ -9,4 +9,5 @@ export { default as HighQuality } from './HighQuality' | |||
| export { default as HybridSearch } from './HybridSearch' | |||
| export { default as ParentChildChunk } from './ParentChildChunk' | |||
| export { default as QuestionAndAnswer } from './QuestionAndAnswer' | |||
| export { default as SearchMenu } from './SearchMenu' | |||
| export { default as VectorSearch } from './VectorSearch' | |||
| @@ -4,14 +4,12 @@ import { useTranslation } from 'react-i18next' | |||
| type BreadcrumbsProps = { | |||
| prefix: string[] | |||
| keywords: string | |||
| resetKeywords: () => void | |||
| searchResultsLength: number | |||
| } | |||
| const Breadcrumbs = ({ | |||
| prefix, | |||
| keywords, | |||
| resetKeywords, | |||
| searchResultsLength, | |||
| }: BreadcrumbsProps) => { | |||
| const { t } = useTranslation() | |||
| @@ -1,51 +1,32 @@ | |||
| import React, { useState } from 'react' | |||
| import React from 'react' | |||
| import Breadcrumbs from './breadcrumbs' | |||
| import Input from '@/app/components/base/input' | |||
| import { useDebounceFn } from 'ahooks' | |||
| import { useTranslation } from 'react-i18next' | |||
| type HeaderProps = { | |||
| prefix: string[] | |||
| inputValue: string | |||
| keywords: string | |||
| resetKeywords: () => void | |||
| updateKeywords: (keywords: string) => void | |||
| searchResultsLength: number | |||
| handleInputChange: React.ChangeEventHandler<HTMLInputElement> | |||
| handleResetKeywords: () => void | |||
| } | |||
| const Header = ({ | |||
| prefix, | |||
| inputValue, | |||
| keywords, | |||
| resetKeywords, | |||
| updateKeywords, | |||
| searchResultsLength, | |||
| handleInputChange, | |||
| handleResetKeywords, | |||
| }: HeaderProps) => { | |||
| const { t } = useTranslation() | |||
| const [inputValue, setInputValue] = useState(keywords) | |||
| const { run: updateKeywordsWithDebounce } = useDebounceFn( | |||
| (keywords: string) => { | |||
| updateKeywords(keywords) | |||
| }, | |||
| { wait: 500 }, | |||
| ) | |||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const keywords = e.target.value | |||
| setInputValue(keywords) | |||
| updateKeywordsWithDebounce(keywords) | |||
| } | |||
| const handleResetKeywords = () => { | |||
| setInputValue('') | |||
| resetKeywords() | |||
| } | |||
| return ( | |||
| <div className='flex items-center gap-x-2 bg-components-panel-bg p-1 pl-3'> | |||
| <Breadcrumbs | |||
| prefix={prefix} | |||
| keywords={keywords} | |||
| resetKeywords={resetKeywords} | |||
| searchResultsLength={searchResultsLength} | |||
| /> | |||
| <Input | |||
| @@ -1,6 +1,8 @@ | |||
| import type { OnlineDriveFile } from '@/models/pipeline' | |||
| import Header from './header' | |||
| import List from './list' | |||
| import { useState } from 'react' | |||
| import { useDebounceFn } from 'ahooks' | |||
| type FileListProps = { | |||
| fileList: OnlineDriveFile[] | |||
| @@ -21,18 +23,41 @@ const FileList = ({ | |||
| updateKeywords, | |||
| searchResultsLength, | |||
| }: FileListProps) => { | |||
| const [inputValue, setInputValue] = useState(keywords) | |||
| const { run: updateKeywordsWithDebounce } = useDebounceFn( | |||
| (keywords: string) => { | |||
| updateKeywords(keywords) | |||
| }, | |||
| { wait: 500 }, | |||
| ) | |||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const keywords = e.target.value | |||
| setInputValue(keywords) | |||
| updateKeywordsWithDebounce(keywords) | |||
| } | |||
| const handleResetKeywords = () => { | |||
| setInputValue('') | |||
| resetKeywords() | |||
| } | |||
| return ( | |||
| <div className='flex h-[400px] flex-col overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg shadow-xs shadow-shadow-shadow-3'> | |||
| <Header | |||
| prefix={prefix} | |||
| inputValue={inputValue} | |||
| keywords={keywords} | |||
| resetKeywords={resetKeywords} | |||
| updateKeywords={updateKeywords} | |||
| handleInputChange={handleInputChange} | |||
| searchResultsLength={searchResultsLength} | |||
| handleResetKeywords={handleResetKeywords} | |||
| /> | |||
| <List | |||
| fileList={fileList} | |||
| selectedFileList={selectedFileList} | |||
| keywords={keywords} | |||
| handleResetKeywords={handleResetKeywords} | |||
| /> | |||
| </div> | |||
| ) | |||
| @@ -0,0 +1,14 @@ | |||
| import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| const EmptyFolder = () => { | |||
| const { t } = useTranslation() | |||
| return ( | |||
| <div className='flex size-full items-center justify-center rounded-[10px] bg-background-section px-1 py-1.5'> | |||
| <span className='system-xs-regular text-text-tertiary'>{t('datasetPipeline.onlineDrive.emptyFolder')}</span> | |||
| </div> | |||
| ) | |||
| } | |||
| export default React.memo(EmptyFolder) | |||
| @@ -0,0 +1,35 @@ | |||
| import Button from '@/app/components/base/button' | |||
| import { SearchMenu } from '@/app/components/base/icons/src/vender/knowledge' | |||
| import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| type EmptySearchResultProps = { | |||
| onResetKeywords: () => void | |||
| } | |||
| const EmptySearchResult = ({ | |||
| onResetKeywords, | |||
| }: EmptySearchResultProps & { | |||
| className?: string | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| return ( | |||
| <div className='flex size-full flex-col items-center justify-center gap-y-2 rounded-[10px] bg-background-section p-6'> | |||
| <SearchMenu className='size-8 text-text-tertiary' /> | |||
| <div className='system-sm-regular text-text-secondary'> | |||
| {t('datasetPipeline.onlineDrive.emptySearchResult')} | |||
| </div> | |||
| <Button | |||
| variant='secondary-accent' | |||
| size='small' | |||
| onClick={onResetKeywords} | |||
| className='px-1.5' | |||
| > | |||
| <span className='px-[3px]'>{t('datasetPipeline.onlineDrive.resetKeywords')}</span> | |||
| </Button> | |||
| </div> | |||
| ) | |||
| } | |||
| export default React.memo(EmptySearchResult) | |||
| @@ -1,33 +1,55 @@ | |||
| import type { OnlineDriveFile } from '@/models/pipeline' | |||
| import Item from './item' | |||
| import EmptyFolder from './empty-folder' | |||
| import EmptySearchResult from './empty-search-result' | |||
| type FileListProps = { | |||
| fileList: OnlineDriveFile[] | |||
| selectedFileList: string[] | |||
| keywords: string | |||
| handleResetKeywords: () => void | |||
| } | |||
| const List = ({ | |||
| fileList, | |||
| selectedFileList, | |||
| keywords, | |||
| handleResetKeywords, | |||
| }: FileListProps) => { | |||
| const isEmptyFolder = fileList.length === 0 && keywords.length === 0 | |||
| const isSearchResultEmpty = fileList.length === 0 && keywords.length > 0 | |||
| return ( | |||
| <div className='grow overflow-y-auto p-1 pt-0'> | |||
| <div className='flex flex-col gap-y-px px-1 py-1.5'> | |||
| { | |||
| fileList.map((file) => { | |||
| const isSelected = selectedFileList.includes(file.key) | |||
| return ( | |||
| <Item | |||
| key={file.key} | |||
| file={file} | |||
| isSelected={isSelected} | |||
| onSelect={(file) => { console.log(file) }} | |||
| onOpen={(file) => { console.log(file) }} | |||
| /> | |||
| ) | |||
| }) | |||
| } | |||
| </div> | |||
| <div className='grow overflow-hidden p-1 pt-0'> | |||
| { | |||
| isEmptyFolder && ( | |||
| <EmptyFolder /> | |||
| ) | |||
| } | |||
| { | |||
| isSearchResultEmpty && ( | |||
| <EmptySearchResult onResetKeywords={handleResetKeywords} /> | |||
| ) | |||
| } | |||
| {fileList.length > 0 && ( | |||
| <div className='flex h-full flex-col gap-y-px overflow-y-auto rounded-[10px] bg-background-section px-1 py-1.5'> | |||
| { | |||
| fileList.map((file) => { | |||
| const isSelected = selectedFileList.includes(file.key) | |||
| return ( | |||
| <Item | |||
| key={file.key} | |||
| file={file} | |||
| isSelected={isSelected} | |||
| onSelect={(file) => { console.log(file) }} | |||
| onOpen={(file) => { console.log(file) }} | |||
| disabled | |||
| /> | |||
| ) | |||
| }) | |||
| } | |||
| </div> | |||
| )} | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -115,11 +115,12 @@ const translation = { | |||
| breadcrumbs: { | |||
| allBuckets: 'All Cloud Storage Buckets', | |||
| searchResult: 'Find {{searchResultsLength}} items in "{{folderName}}" folder', | |||
| noSearchResult: 'No items were found', | |||
| resetKeywords: 'Reset keywords', | |||
| searchPlaceholder: 'Search files...', | |||
| }, | |||
| notSupportedFileType: 'This file type is not supported', | |||
| emptyFolder: 'This folder is empty', | |||
| emptySearchResult: 'No items were found', | |||
| resetKeywords: 'Reset keywords', | |||
| }, | |||
| } | |||
| @@ -115,11 +115,12 @@ const translation = { | |||
| breadcrumbs: { | |||
| allBuckets: '所有云存储桶', | |||
| searchResult: '在 "{{folderName}}" 文件夹中找到 {{searchResultsLength}} 个项目', | |||
| noSearchResult: '未找到项目', | |||
| resetKeywords: '重置关键词', | |||
| searchPlaceholder: '搜索文件...', | |||
| }, | |||
| notSupportedFileType: '不支持此文件类型', | |||
| emptyFolder: '此文件夹为空', | |||
| emptySearchResult: '未找到任何项目', | |||
| resetKeywords: '重置关键词', | |||
| }, | |||
| } | |||