Knowledge optimization (#3755)

Co-authored-by: crazywoola <427733928@qq.com>
Co-authored-by: JzoNg <jzongcode@gmail.com>
This commit is contained in:
Jyong
2024-04-24 15:02:29 +08:00
committed by GitHub
parent 3cd8e6f5c6
commit f257f2c396
75 changed files with 2756 additions and 266 deletions

View File

@@ -2,7 +2,7 @@
import { useContext, useContextSelector } from 'use-context-selector'
import { useRouter } from 'next/navigation'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import s from './style.module.css'
@@ -22,9 +22,12 @@ import { useProviderContext } from '@/context/provider-context'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import EditAppModal from '@/app/components/explore/create-app-modal'
import SwitchAppModal from '@/app/components/app/switch-app-modal'
import type { Tag } from '@/app/components/base/tag-management/constant'
import TagSelector from '@/app/components/base/tag-management/selector'
export type AppCardProps = {
app: App
@@ -142,6 +145,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
}
const Operations = (props: HtmlContentProps) => {
const onMouseLeave = async () => {
props.onClose?.()
}
const onClickSettings = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
props.onClick?.()
@@ -173,7 +179,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
setShowConfirmDelete(true)
}
return (
<div className="relative w-full py-1">
<div className="relative w-full py-1" onMouseLeave={onMouseLeave}>
<button className={s.actionItem} onClick={onClickSettings}>
<span className={s.actionName}>{t('app.editApp')}</span>
</button>
@@ -208,6 +214,11 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
)
}
const [tags, setTags] = useState<Tag[]>(app.tags)
useEffect(() => {
setTags(app.tags)
}, [app.tags])
return (
<>
<div
@@ -254,27 +265,71 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
{app.mode === 'completion' && <div className='truncate'>{t('app.types.completion').toUpperCase()}</div>}
</div>
</div>
{isCurrentWorkspaceManager && <CustomPopover
htmlContent={<Operations />}
position="br"
trigger="click"
btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
btnClassName={open =>
cn(
open ? '!bg-gray-100 !shadow-none' : '!bg-transparent',
'!hidden h-8 w-8 !p-2 rounded-md border-none hover:!bg-gray-100 group-hover:!inline-flex',
)
}
className={'!w-[128px] h-fit !z-20'}
popupClassName={
(app.mode === 'completion' || app.mode === 'chat')
? '!w-[238px] translate-x-[-110px]'
: ''
}
manualClose
/>}
</div>
<div className='mb-1 px-[14px] text-xs leading-normal text-gray-500 line-clamp-4'>{app.description}</div>
<div
className={cn(
'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-gray-500 group-hover:line-clamp-2 group-hover:max-h-[36px]',
tags.length ? 'line-clamp-2' : 'line-clamp-4',
)}
title={app.description}
>
{app.description}
</div>
<div className={cn(
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}>
<div className={cn('grow flex items-center gap-1 w-0')} onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}>
<div className={cn(
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
tags.length ? '!block' : '!hidden',
)}>
<TagSelector
position='bl'
type='app'
targetID={app.id}
value={tags.map(tag => tag.id)}
selectedTags={tags}
onCacheUpdate={setTags}
onChange={onRefresh}
/>
</div>
</div>
{isCurrentWorkspaceManager && (
<>
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200'/>
<div className='!hidden group-hover:!flex shrink-0'>
<CustomPopover
htmlContent={<Operations />}
position="br"
trigger="click"
btnElement={
<div
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
>
<DotsHorizontal className='w-4 h-4 text-gray-700' />
</div>
}
btnClassName={open =>
cn(
open ? '!bg-black/5 !shadow-none' : '!bg-transparent',
'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5',
)
}
popupClassName={
(app.mode === 'completion' || app.mode === 'chat')
? '!w-[238px] translate-x-[-110px]'
: ''
}
className={'!w-[128px] h-fit !z-20'}
/>
</div>
</>
)}
</div>
{showEditModal && (
<EditAppModal
isEditModal

View File

@@ -13,19 +13,22 @@ import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { CheckModal } from '@/hooks/use-pay'
import TabSliderNew from '@/app/components/base/tab-slider-new'
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import { DotsGrid, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { DotsGrid } from '@/app/components/base/icons/src/vender/line/general'
import {
// AiText,
ChatBot,
CuteRobot,
} from '@/app/components/base/icons/src/vender/line/communication'
import { Route } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
import SearchInput from '@/app/components/base/search-input'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
import TagManagementModal from '@/app/components/base/tag-management'
import TagFilter from '@/app/components/base/tag-management/filter'
const getKey = (
pageIndex: number,
previousPageData: AppListResponse,
activeTab: string,
tags: string[],
keywords: string,
) => {
if (!pageIndex || previousPageData.has_more) {
@@ -36,6 +39,9 @@ const getKey = (
else
delete params.params.mode
if (tags.length)
params.params.tag_ids = tags
return params
}
return null
@@ -44,14 +50,17 @@ const getKey = (
const Apps = () => {
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'all',
})
const [tagFilterValue, setTagFilterValue] = useState<string[]>([])
const [tagIDs, setTagIDs] = useState<string[]>([])
const [keywords, setKeywords] = useState('')
const [searchKeywords, setSearchKeywords] = useState('')
const { data, isLoading, setSize, mutate } = useSWRInfinite(
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, searchKeywords),
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, tagIDs, searchKeywords),
fetchAppList,
{ revalidateFirstPage: true },
)
@@ -61,7 +70,6 @@ const Apps = () => {
{ value: 'all', text: t('app.types.all'), icon: <DotsGrid className='w-[14px] h-[14px] mr-1'/> },
{ value: 'chat', text: t('app.types.chatbot'), icon: <ChatBot className='w-[14px] h-[14px] mr-1'/> },
{ value: 'agent-chat', text: t('app.types.agent'), icon: <CuteRobot className='w-[14px] h-[14px] mr-1'/> },
// { value: 'completion', text: t('app.newApp.completeApp'), icon: <AiText className='w-[14px] h-[14px] mr-1'/> },
{ value: 'workflow', text: t('app.types.workflow'), icon: <Route className='w-[14px] h-[14px] mr-1'/> },
]
@@ -88,14 +96,17 @@ const Apps = () => {
const { run: handleSearch } = useDebounceFn(() => {
setSearchKeywords(keywords)
}, { wait: 500 })
const handleKeywordsChange = (value: string) => {
setKeywords(value)
handleSearch()
}
const handleClear = () => {
handleKeywordsChange('')
const { run: handleTagsUpdate } = useDebounceFn(() => {
setTagIDs(tagFilterValue)
}, { wait: 500 })
const handleTagsChange = (value: string[]) => {
setTagFilterValue(value)
handleTagsUpdate()
}
return (
@@ -106,31 +117,9 @@ const Apps = () => {
onChange={setActiveTab}
options={options}
/>
<div className="flex items-center px-2 w-[200px] h-8 rounded-lg bg-gray-200">
<div className="pointer-events-none shrink-0 flex items-center mr-1.5 justify-center w-4 h-4">
<SearchLg className="h-3.5 w-3.5 text-gray-500" aria-hidden="true" />
</div>
<input
type="text"
name="query"
className="grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-600 text-[13px] placeholder:text-gray-500 appearance-none outline-none"
placeholder={t('common.operation.search')!}
value={keywords}
onChange={(e) => {
handleKeywordsChange(e.target.value)
}}
autoComplete="off"
/>
{
keywords && (
<div
className='shrink-0 flex items-center justify-center w-4 h-4 cursor-pointer'
onClick={handleClear}
>
<XCircle className='w-3.5 h-3.5 text-gray-400' />
</div>
)
}
<div className='flex items-center gap-2'>
<TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} />
<SearchInput className='w-[200px]' value={keywords} onChange={handleKeywordsChange} />
</div>
</div>
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
@@ -142,6 +131,9 @@ const Apps = () => {
<CheckModal />
</nav>
<div ref={anchorRef} className='h-0'> </div>
{showTagManagementModal && (
<TagManagementModal type='app' show={showTagManagementModal} />
)}
</>
)
}

View File

@@ -1,8 +1,9 @@
'use client'
// Libraries
import { useRef } from 'react'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
import useSWR from 'swr'
// Components
@@ -11,15 +12,20 @@ import DatasetFooter from './DatasetFooter'
import ApiServer from './ApiServer'
import Doc from './Doc'
import TabSliderNew from '@/app/components/base/tab-slider-new'
import SearchInput from '@/app/components/base/search-input'
import TagManagementModal from '@/app/components/base/tag-management'
import TagFilter from '@/app/components/base/tag-management/filter'
// Services
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
// Hooks
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
const Container = () => {
const { t } = useTranslation()
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
const options = [
{ value: 'dataset', text: t('dataset.datasets') },
@@ -32,6 +38,25 @@ const Container = () => {
const containerRef = useRef<HTMLDivElement>(null)
const { data } = useSWR(activeTab === 'dataset' ? null : '/datasets/api-base-info', fetchDatasetApiBaseUrl)
const [keywords, setKeywords] = useState('')
const [searchKeywords, setSearchKeywords] = useState('')
const { run: handleSearch } = useDebounceFn(() => {
setSearchKeywords(keywords)
}, { wait: 500 })
const handleKeywordsChange = (value: string) => {
setKeywords(value)
handleSearch()
}
const [tagFilterValue, setTagFilterValue] = useState<string[]>([])
const [tagIDs, setTagIDs] = useState<string[]>([])
const { run: handleTagsUpdate } = useDebounceFn(() => {
setTagIDs(tagFilterValue)
}, { wait: 500 })
const handleTagsChange = (value: string[]) => {
setTagFilterValue(value)
handleTagsUpdate()
}
return (
<div ref={containerRef} className='grow relative flex flex-col bg-gray-100 overflow-y-auto'>
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-10 flex-wrap gap-y-2'>
@@ -40,13 +65,22 @@ const Container = () => {
onChange={newActiveTab => setActiveTab(newActiveTab)}
options={options}
/>
{activeTab === 'dataset' && (
<div className='flex items-center gap-2'>
<TagFilter type='knowledge' value={tagFilterValue} onChange={handleTagsChange} />
<SearchInput className='w-[200px]' value={keywords} onChange={handleKeywordsChange} />
</div>
)}
{activeTab === 'api' && data && <ApiServer apiBaseUrl={data.api_base_url || ''} />}
</div>
{activeTab === 'dataset' && (
<>
<Datasets containerRef={containerRef} />
<Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} />
<DatasetFooter />
{showTagManagementModal && (
<TagManagementModal type='knowledge' show={showTagManagementModal} />
)}
</>
)}

View File

@@ -2,41 +2,44 @@
import { useContext } from 'use-context-selector'
import Link from 'next/link'
import type { MouseEventHandler } from 'react'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import style from '../list.module.css'
import Confirm from '@/app/components/base/confirm'
import { ToastContext } from '@/app/components/base/toast'
import { deleteDataset } from '@/service/datasets'
import AppIcon from '@/app/components/base/app-icon'
import type { DataSet } from '@/models/datasets'
import Tooltip from '@/app/components/base/tooltip'
import { Folder } from '@/app/components/base/icons/src/vender/solid/files'
import type { HtmlContentProps } from '@/app/components/base/popover'
import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider'
import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
import RenameDatasetModal from '@/app/components/datasets/rename-modal'
import type { Tag } from '@/app/components/base/tag-management/constant'
import TagSelector from '@/app/components/base/tag-management/selector'
export type DatasetCardProps = {
dataset: DataSet
onDelete?: () => void
onSuccess?: () => void
}
const DatasetCard = ({
dataset,
onDelete,
onSuccess,
}: DatasetCardProps) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [tags, setTags] = useState<Tag[]>(dataset.tags)
const [showRenameModal, setShowRenameModal] = useState(false)
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const onDeleteClick: MouseEventHandler = useCallback((e) => {
e.preventDefault()
setShowConfirmDelete(true)
}, [])
const onConfirmDelete = useCallback(async () => {
try {
await deleteDataset(dataset.id)
notify({ type: 'success', message: t('dataset.datasetDeleted') })
if (onDelete)
onDelete()
if (onSuccess)
onSuccess()
}
catch (e: any) {
notify({ type: 'error', message: `${t('dataset.datasetDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}` })
@@ -44,53 +47,158 @@ const DatasetCard = ({
setShowConfirmDelete(false)
}, [dataset.id])
const Operations = (props: HtmlContentProps) => {
const onMouseLeave = async () => {
props.onClose?.()
}
const onClickRename = async (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
props.onClick?.()
e.preventDefault()
setShowRenameModal(true)
}
const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
props.onClick?.()
e.preventDefault()
setShowConfirmDelete(true)
}
return (
<div className="relative w-full py-1" onMouseLeave={onMouseLeave}>
<div className='h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer' onClick={onClickRename}>
<span className='text-gray-700 text-sm'>{t('common.operation.settings')}</span>
</div>
<Divider className="!my-1" />
<div
className='group h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-red-50 rounded-lg cursor-pointer'
onClick={onClickDelete}
>
<span className={cn('text-gray-700 text-sm', 'group-hover:text-red-500')}>
{t('common.operation.delete')}
</span>
</div>
</div>
)
}
useEffect(() => {
setTags(dataset.tags)
}, [dataset])
return (
<>
<Link href={`/datasets/${dataset.id}/documents`} className={cn(style.listItem)} data-disable-nprogress={true}>
<div className={style.listItemTitle}>
<AppIcon size='small' className={cn(!dataset.embedding_available && style.unavailable)} />
<div className={cn(style.listItemHeading, !dataset.embedding_available && style.unavailable)}>
<div className={style.listItemHeadingContent}>
{dataset.name}
<Link
href={`/datasets/${dataset.id}/documents`}
className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
data-disable-nprogress={true}
>
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
<div className={cn(
'shrink-0 flex items-center justify-center p-2.5 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]',
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
)}>
<Folder className='w-5 h-5 text-[#444CE7]' />
</div>
<div className='grow w-0 py-[1px]'>
<div className='flex items-center text-sm leading-5 font-semibold text-gray-800'>
<div className={cn('truncate', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} title={dataset.name}>{dataset.name}</div>
{!dataset.embedding_available && (
<Tooltip
selector={`dataset-tag-${dataset.id}`}
htmlContent={t('dataset.unavailableTip')}
>
<span className='shrink-0 inline-flex w-max ml-1 px-1 border boder-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span>
</Tooltip>
)}
</div>
<div className='flex items-center mt-[1px] text-xs leading-[18px] text-gray-500'>
<div
className={cn('truncate', (!dataset.embedding_available || !dataset.document_count) && 'opacity-50')}
title={`${dataset.document_count}${t('dataset.documentCount')} · ${Math.round(dataset.word_count / 1000)}${t('dataset.wordCount')} · ${dataset.app_count}${t('dataset.appCount')}`}
>
<span>{dataset.document_count}{t('dataset.documentCount')}</span>
<span className='shrink-0 mx-0.5 w-1 text-gray-400'>·</span>
<span>{Math.round(dataset.word_count / 1000)}{t('dataset.wordCount')}</span>
<span className='shrink-0 mx-0.5 w-1 text-gray-400'>·</span>
<span>{dataset.app_count}{t('dataset.appCount')}</span>
</div>
</div>
</div>
{!dataset.embedding_available && (
<Tooltip
selector={`dataset-tag-${dataset.id}`}
htmlContent={t('dataset.unavailableTip')}
>
<span className='px-1 border boder-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span>
</Tooltip>
</div>
<div
className={cn(
'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-gray-500 group-hover:line-clamp-2 group-hover:max-h-[36px]',
tags.length ? 'line-clamp-2' : 'line-clamp-4',
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
)}
<span className={style.deleteDatasetIcon} onClick={onDeleteClick} />
title={dataset.description}>
{dataset.description}
</div>
<div className={cn(style.listItemDescription, !dataset.embedding_available && style.unavailable)}>{dataset.description}</div>
<div className={cn(style.listItemFooter, style.datasetCardFooter, !dataset.embedding_available && style.unavailable)}>
<span className={style.listItemStats}>
<span className={cn(style.listItemFooterIcon, style.docIcon)} />
{dataset.document_count}{t('dataset.documentCount')}
</span>
<span className={style.listItemStats}>
<span className={cn(style.listItemFooterIcon, style.textIcon)} />
{Math.round(dataset.word_count / 1000)}{t('dataset.wordCount')}
</span>
<span className={style.listItemStats}>
<span className={cn(style.listItemFooterIcon, style.applicationIcon)} />
{dataset.app_count}{t('dataset.appCount')}
</span>
<div className={cn(
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}>
<div className={cn('grow flex items-center gap-1 w-0', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}>
<div className={cn(
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
tags.length ? '!block' : '!hidden',
)}>
<TagSelector
position='bl'
type='knowledge'
targetID={dataset.id}
value={tags.map(tag => tag.id)}
selectedTags={tags}
onCacheUpdate={setTags}
onChange={onSuccess}
/>
</div>
</div>
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200'/>
<div className='!hidden group-hover:!flex shrink-0'>
<CustomPopover
htmlContent={<Operations />}
position="br"
trigger="click"
btnElement={
<div
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
>
<DotsHorizontal className='w-4 h-4 text-gray-700' />
</div>
}
btnClassName={open =>
cn(
open ? '!bg-black/5 !shadow-none' : '!bg-transparent',
'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5',
)
}
className={'!w-[128px] h-fit !z-20'}
/>
</div>
</div>
{showConfirmDelete && (
<Confirm
title={t('dataset.deleteDatasetConfirmTitle')}
content={t('dataset.deleteDatasetConfirmContent')}
isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
/>
)}
</Link>
{showRenameModal && (
<RenameDatasetModal
show={showRenameModal}
dataset={dataset}
onClose={() => setShowRenameModal(false)}
onSuccess={onSuccess}
/>
)}
{showConfirmDelete && (
<Confirm
title={t('dataset.deleteDatasetConfirmTitle')}
content={t('dataset.deleteDatasetConfirmContent')}
isShow={showConfirmDelete}
onClose={() => setShowConfirmDelete(false)}
onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
/>
)}
</>
)
}

View File

@@ -10,21 +10,46 @@ import type { DataSetListResponse } from '@/models/datasets'
import { fetchDatasets } from '@/service/datasets'
import { useAppContext } from '@/context/app-context'
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
if (!pageIndex || previousPageData.has_more)
return { url: 'datasets', params: { page: pageIndex + 1, limit: 30 } }
const getKey = (
pageIndex: number,
previousPageData: DataSetListResponse,
tags: string[],
keyword: string,
) => {
if (!pageIndex || previousPageData.has_more) {
const params: any = {
url: 'datasets',
params: {
page: pageIndex + 1,
limit: 30,
},
}
if (tags.length)
params.params.tag_ids = tags
if (keyword)
params.params.keyword = keyword
return params
}
return null
}
type Props = {
containerRef: React.RefObject<HTMLDivElement>
tags: string[]
keywords: string
}
const Datasets = ({
containerRef,
tags,
keywords,
}: Props) => {
const { isCurrentWorkspaceManager } = useAppContext()
const { data, isLoading, setSize, mutate } = useSWRInfinite(getKey, fetchDatasets, { revalidateFirstPage: false, revalidateAll: true })
const { data, isLoading, setSize, mutate } = useSWRInfinite(
(pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords),
fetchDatasets,
{ revalidateFirstPage: false, revalidateAll: true },
)
const loadingStateRef = useRef(false)
const anchorRef = useRef<HTMLAnchorElement>(null)
@@ -53,7 +78,7 @@ const Datasets = ({
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
{ isCurrentWorkspaceManager && <NewDatasetCard ref={anchorRef} /> }
{data?.map(({ data: datasets }) => datasets.map(dataset => (
<DatasetCard key={dataset.id} dataset={dataset} onDelete={mutate} />),
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
))}
</nav>
)

View File

@@ -1,27 +1,22 @@
'use client'
import { forwardRef } from 'react'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import style from '../list.module.css'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
const CreateAppCard = forwardRef<HTMLAnchorElement>((_, ref) => {
const { t } = useTranslation()
return (
<Link ref={ref} className={classNames(style.listItem, style.newItemCard)} href='/datasets/create'>
<div className={style.listItemTitle}>
<span className={style.newItemIcon}>
<span className={classNames(style.newItemIconImage, style.newItemIconAdd)} />
</span>
<div className={classNames(style.listItemHeading, style.newItemCardHeading)}>
{t('dataset.createDataset')}
<a ref={ref} className='group flex flex-col col-span-1 bg-gray-200 border-[0.5px] border-black/5 rounded-xl min-h-[160px] transition-all duration-200 ease-in-out cursor-pointer hover:bg-white hover:shadow-lg' href='/datasets/create'>
<div className='shrnik-0 flex items-center p-4 pb-3'>
<div className='w-10 h-10 flex items-center justify-center border border-gray-200 bg-gray-100 rounded-lg'>
<Plus className='w-4 h-4 text-gray-500'/>
</div>
<div className='ml-3 text-sm font-semibold leading-5 text-gray-800 group-hover:text-primary-600'>{t('dataset.createDataset')}</div>
</div>
<div className={style.listItemDescription}>{t('dataset.createDatasetIntro')}</div>
{/* <div className='text-xs text-gray-500'>{t('app.createFromConfigFile')}</div> */}
</Link>
<div className='mb-1 px-4 text-xs leading-normal text-gray-500 line-clamp-4'>{t('dataset.createDatasetIntro')}</div>
</a>
)
})

View File

@@ -1,6 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.2 1.5H2.3C2.01997 1.5 1.87996 1.5 1.773 1.5545C1.67892 1.60243 1.60243 1.67892 1.5545 1.773C1.5 1.87996 1.5 2.01997 1.5 2.3V4.2C1.5 4.48003 1.5 4.62004 1.5545 4.727C1.60243 4.82108 1.67892 4.89757 1.773 4.9455C1.87996 5 2.01997 5 2.3 5H4.2C4.48003 5 4.62004 5 4.727 4.9455C4.82108 4.89757 4.89757 4.82108 4.9455 4.727C5 4.62004 5 4.48003 5 4.2V2.3C5 2.01997 5 1.87996 4.9455 1.773C4.89757 1.67892 4.82108 1.60243 4.727 1.5545C4.62004 1.5 4.48003 1.5 4.2 1.5Z" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.7 1.5H7.8C7.51997 1.5 7.37996 1.5 7.273 1.5545C7.17892 1.60243 7.10243 1.67892 7.0545 1.773C7 1.87996 7 2.01997 7 2.3V4.2C7 4.48003 7 4.62004 7.0545 4.727C7.10243 4.82108 7.17892 4.89757 7.273 4.9455C7.37996 5 7.51997 5 7.8 5H9.7C9.98003 5 10.12 5 10.227 4.9455C10.3211 4.89757 10.3976 4.82108 10.4455 4.727C10.5 4.62004 10.5 4.48003 10.5 4.2V2.3C10.5 2.01997 10.5 1.87996 10.4455 1.773C10.3976 1.67892 10.3211 1.60243 10.227 1.5545C10.12 1.5 9.98003 1.5 9.7 1.5Z" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.7 7H7.8C7.51997 7 7.37996 7 7.273 7.0545C7.17892 7.10243 7.10243 7.17892 7.0545 7.273C7 7.37996 7 7.51997 7 7.8V9.7C7 9.98003 7 10.12 7.0545 10.227C7.10243 10.3211 7.17892 10.3976 7.273 10.4455C7.37996 10.5 7.51997 10.5 7.8 10.5H9.7C9.98003 10.5 10.12 10.5 10.227 10.4455C10.3211 10.3976 10.3976 10.3211 10.4455 10.227C10.5 10.12 10.5 9.98003 10.5 9.7V7.8C10.5 7.51997 10.5 7.37996 10.4455 7.273C10.3976 7.17892 10.3211 7.10243 10.227 7.0545C10.12 7 9.98003 7 9.7 7Z" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.2 7H2.3C2.01997 7 1.87996 7 1.773 7.0545C1.67892 7.10243 1.60243 7.17892 1.5545 7.273C1.5 7.37996 1.5 7.51997 1.5 7.8V9.7C1.5 9.98003 1.5 10.12 1.5545 10.227C1.60243 10.3211 1.67892 10.3976 1.773 10.4455C1.87996 10.5 2.01997 10.5 2.3 10.5H4.2C4.48003 10.5 4.62004 10.5 4.727 10.4455C4.82108 10.3976 4.89757 10.3211 4.9455 10.227C5 10.12 5 9.98003 5 9.7V7.8C5 7.51997 5 7.37996 4.9455 7.273C4.89757 7.17892 4.82108 7.10243 4.727 7.0545C4.62004 7 4.48003 7 4.2 7Z" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,3 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1H3C2.73478 1 2.48043 1.10536 2.29289 1.29289C2.10536 1.48043 2 1.73478 2 2V10C2 10.2652 2.10536 10.5196 2.29289 10.7071C2.48043 10.8946 2.73478 11 3 11H9C9.26522 11 9.51957 10.8946 9.70711 10.7071C9.89464 10.5196 10 10.2652 10 10V4M7 1L10 4M7 1V4H10M8 6.5H4M8 8.5H4M5 4.5H4" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 457 B

View File

@@ -1,3 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1H3C2.73478 1 2.48043 1.10536 2.29289 1.29289C2.10536 1.48043 2 1.73478 2 2V10C2 10.2652 2.10536 10.5196 2.29289 10.7071C2.48043 10.8946 2.73478 11 3 11H9C9.26522 11 9.51957 10.8946 9.70711 10.7071C9.89464 10.5196 10 10.2652 10 10V4M7 1L10 4M7 1V4H10M8 6.5H4M8 8.5H4M5 4.5H4" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 457 B

View File

@@ -159,18 +159,6 @@
background-image: url("./apps/assets/completion-solid.svg");
}
.docIcon {
background-image: url("./datasets/assets/doc.svg");
}
.textIcon {
background-image: url("./datasets/assets/text.svg");
}
.applicationIcon {
background-image: url("./datasets/assets/application.svg");
}
.newItemCardHeading {
@apply transition-colors duration-200 ease-in-out;
}