feat: dataset metadata

This commit is contained in:
Joel 2025-02-14 15:28:18 +08:00
parent 5e2bd407a8
commit 0ed892a747
6 changed files with 239 additions and 19 deletions

View File

@ -22,6 +22,8 @@ import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/d
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
import CreateModal from '@/app/components/datasets/metadata/create-metadata-modal'
import SelectMetadataModal from '@/app/components/datasets/metadata/select-metadata-modal'
import DatasetMetadataDrawer from '@/app/components/datasets/metadata/dataset-metadata-drawer'
// Services
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
@ -30,6 +32,7 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
import { useAppContext } from '@/context/app-context'
import { useExternalApiPanel } from '@/context/external-api-panel-context'
import { DataType } from '@/app/components/datasets/metadata/types'
const Container = () => {
const { t } = useTranslation()
@ -82,11 +85,32 @@ const Container = () => {
return router.replace('/apps')
}, [currentWorkspace, router])
const [userMetadata, setUserMetadata] = useState([
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
])
return (
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
<div className='ml-[600px] mt-[300px]'>
<SelectMetadataModal trigger={<Button className='w-[200px]'>select</Button>} onSave={(data) => { console.log(data) }} />
<CreateModal trigger={<Button className='w-[200px]'>add</Button>} hasBack onSave={(data) => { console.log(data) }} />
<Button className='flex w-[200px]' size="medium" onClick={() => setShowExternalApiPanel(true)}>
Metadata
</Button>
<DatasetMetadataDrawer
userMetadata={userMetadata}
onChange={setUserMetadata}
builtInMetadata={[
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
]}
isBuiltInEnabled={false}
onIsBuiltInEnabledChange={() => { }}
onClose={() => { }}
/>
</div>
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
<TabSliderNew

View File

@ -53,15 +53,18 @@ export default function Drawer({
/>
<div className={cn('relative z-50 flex flex-col justify-between bg-components-panel-bg w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
<>
{title && <Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-text-primary"
>
{title}
</Dialog.Title>}
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={onClose} />
</Dialog.Title>}
<div className='flex justify-between'>
{title && <Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-text-primary"
>
{title}
</Dialog.Title>}
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={onClose} />
</Dialog.Title>}
</div>
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
{children}
</>

View File

@ -0,0 +1,187 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import type { MetadataItemWithValueLength } from './types'
import Drawer from '../../base/drawer'
import Button from '@/app/components/base/button'
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
import { getIcon } from './utils/get-icon'
import cn from '@/utils/classnames'
import Modal from '../../base/modal'
import Field from './field'
import Input from '@/app/components/base/input'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import Switch from '../../base/switch'
import Tooltip from '../../base/tooltip'
import CreateModal from '@/app/components/datasets/metadata/create-metadata-modal'
const i18nPrefix = 'dataset.metadata.datasetMetadata'
type Props = {
userMetadata: MetadataItemWithValueLength[]
builtInMetadata: MetadataItemWithValueLength[]
isBuiltInEnabled: boolean
onIsBuiltInEnabledChange: (value: boolean) => void
onClose: () => void
onChange: (data: MetadataItemWithValueLength[]) => void
}
type ItemProps = {
readonly?: boolean
payload: MetadataItemWithValueLength
onRename?: () => void
onDelete?: () => void
}
const Item: FC<ItemProps> = ({
readonly,
payload,
onRename,
onDelete,
}) => {
const Icon = getIcon(payload.type)
const handleRename = useCallback(() => {
onRename?.()
}, [onRename])
const handleDelete = useCallback(() => {
onDelete?.()
}, [onDelete])
return (
<div
key={payload.id}
className={cn(!readonly && 'group/item', 'mx-1 flex items-center h-6 px-3 justify-between rounded-md hover:bg-state-base-hover cursor-pointer')}
>
<div className='w-0 grow flex items-center h-full text-text-secondary'>
<Icon className='shrink-0 mr-[5px] size-3.5' />
<div className='w-0 grow truncate system-sm-medium'>{payload.name}</div>
</div>
<div className='group-hover/item:hidden ml-1 shrink-0 system-xs-regular text-text-tertiary'>
{payload.valueLength || 0} values
</div>
<div className='group-hover/item:flex hidden items-center h-6 px-3 text-text-secondary rounded-md hover:bg-state-base-hover cursor-pointer space-x-1'>
<RiEditLine className='size-4 cursor-pointer' onClick={handleRename} />
<RiDeleteBinLine className='size-4 cursor-pointer' onClick={handleDelete} />
</div>
</div>
)
}
const DatasetMetadataDrawer: FC<Props> = ({
userMetadata,
builtInMetadata,
isBuiltInEnabled,
onIsBuiltInEnabledChange,
onClose,
onChange,
}) => {
const { t } = useTranslation()
const [isShowRenameModal, setIsShowRenameModal] = useState(false)
const [currPayload, setCurrPayload] = useState<MetadataItemWithValueLength | null>(null)
const [templeName, setTempleName] = useState('')
const handleRename = useCallback((payload: MetadataItemWithValueLength) => {
return () => {
setCurrPayload(payload)
setTempleName(payload.name)
setIsShowRenameModal(true)
}
}, [setCurrPayload, setIsShowRenameModal])
const handleAdd = useCallback((data: MetadataItemWithValueLength) => {
const nextUserMetadata = produce(userMetadata, (draft) => {
draft.push(data)
})
onChange(nextUserMetadata)
}, [userMetadata, onChange])
const handleRenamed = useCallback(() => {
const nextUserMetadata = produce(userMetadata, (draft) => {
const index = draft.findIndex(p => p.id === currPayload?.id)
if (index !== -1)
draft[index].name = templeName!
})
onChange(nextUserMetadata)
setIsShowRenameModal(false)
}, [currPayload, templeName, userMetadata, onChange])
const handleDelete = useCallback((payload: MetadataItemWithValueLength) => {
return () => {
const nextUserMetadata = userMetadata.filter(p => p.id !== payload.id)
onChange(nextUserMetadata)
}
}, [userMetadata, onChange])
return (
<Drawer
isOpen={true}
onClose={onClose}
showClose
title='Metadata'
footer={null}
panelClassname='block !max-w-[420px] my-2 rounded-l-2xl'
>
<div className='system-sm-regular text-text-tertiary'>You can manage all metadata in this knowledge here. Modifications will be synchronized to every document.</div>
<CreateModal trigger={<Button variant='primary' className='mt-3'>
<RiAddLine className='mr-1' />
Add Metadata
</Button>} hasBack onSave={handleAdd} />
{userMetadata.map(payload => (
<Item
key={payload.id}
payload={payload}
onRename={handleRename(payload)}
onDelete={handleDelete(payload)}
/>
))}
<div className='flex'>
<Switch
defaultValue={isBuiltInEnabled}
onChange={onIsBuiltInEnabledChange}
/>
<div>Built-in</div>
<Tooltip popupContent="xxx" />
</div>
{builtInMetadata.map(payload => (
<Item
key={payload.id}
readonly
payload={payload}
/>
))}
{isShowRenameModal && (
<Modal isShow title="rename">
<Field label={t(`${i18nPrefix}.name`)}>
<Input
value={templeName}
onChange={e => setTempleName(e.target.value)}
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
/>
</Field>
<div className='mt-4 flex justify-end'>
<Button
className='mr-2'
onClick={() => {
setIsShowRenameModal(false)
setTempleName(currPayload!.name)
}}>{t('common.operation.cancel')}</Button>
<Button
onClick={handleRenamed}
variant='primary'
disabled={!templeName}
>{t('common.operation.save')}</Button>
</div>
</Modal>
)}
</Drawer>
)
}
export default React.memo(DatasetMetadataDrawer)

View File

@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { DataType } from './types'
import type { MetadataItem } from './types'
import SearchInput from '../../base/search-input'
import { RiAddLine, RiArrowRightUpLine, RiHashtag, RiTextSnippet, RiTimeLine } from '@remixicon/react'
import { RiAddLine, RiArrowRightUpLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { getIcon } from './utils/get-icon'
const i18nPrefix = 'dataset.metadata.selectMetadata'
@ -16,14 +16,6 @@ type Props = {
onManage: () => void
}
const getIcon = (type: DataType) => {
return ({
[DataType.string]: RiTextSnippet,
[DataType.number]: RiHashtag,
[DataType.time]: RiTimeLine,
}[type] || RiTextSnippet)
}
const SelectMetadata: FC<Props> = ({
list,
onSelect,

View File

@ -9,3 +9,7 @@ export type MetadataItem = {
type: DataType
name: string
}
export type MetadataItemWithValueLength = MetadataItem & {
valueLength: number
}

View File

@ -0,0 +1,10 @@
import { DataType } from '../types'
import { RiHashtag, RiTextSnippet, RiTimeLine } from '@remixicon/react'
export const getIcon = (type: DataType) => {
return ({
[DataType.string]: RiTextSnippet,
[DataType.number]: RiHashtag,
[DataType.time]: RiTimeLine,
}[type] || RiTextSnippet)
}