Compare commits

...

25 Commits

Author SHA1 Message Date
Joel
3107ec878a chore: edit metadata batch i18n 2025-02-20 16:06:10 +08:00
Joel
7d5fcfef4c feat: document dataset i18n 2025-02-20 15:22:13 +08:00
Joel
e862ab0def feat: document and tech params 2025-02-20 14:54:02 +08:00
Joel
15f80a72b8 feat: add popup 2025-02-20 11:45:36 +08:00
Joel
1a7de23864 feat: doc metadata can show edit and cancel 2025-02-20 11:00:38 +08:00
Joel
10fccd2b3f feat: metadata panel 2025-02-19 18:38:29 +08:00
Joel
b568947e00 feat: no data 2025-02-17 17:33:44 +08:00
Joel
7dcbb75839 chore: rename 2025-02-17 17:00:05 +08:00
Joel
ffdfcdd4a4 feat: apply to all selected 2025-02-17 16:57:59 +08:00
Joel
f4604bf6d0 feat: new meta data 2025-02-17 15:12:38 +08:00
Joel
3a72b76c32 file 2025-02-17 14:30:36 +08:00
Joel
49dd77e219 feat: edit beacon 2025-02-17 14:30:23 +08:00
Joel
b9f223d9d4 feat: modal row 2025-02-17 12:53:34 +08:00
Joel
3c4da03575 feat: eidt row item 2025-02-17 11:38:12 +08:00
Joel
7692476097 feat: edit modal struct 2025-02-14 17:58:19 +08:00
Joel
428438eeca feat: i18n 2025-02-14 16:39:31 +08:00
Joel
b7c546f2ad feat: dataset metadata collection 2025-02-14 16:24:49 +08:00
Joel
0ed892a747 feat: dataset metadata 2025-02-14 15:28:18 +08:00
Joel
5e2bd407a8 chore: select i18n and other 2025-02-14 14:01:03 +08:00
Joel
a4668e0ffc fix: manage btn hover 2025-02-14 11:21:48 +08:00
Joel
1ca79ea729 feat: select metadata 2025-02-14 11:18:40 +08:00
Joel
ebb6de5f52 feat: split main conten 2025-02-13 16:48:02 +08:00
Joel
2adc704463 feat: add back button 2025-02-13 16:19:30 +08:00
Joel
b74f1b3c07 chore: i18n 2025-02-13 15:50:18 +08:00
Joel
f60e650400 feat: create metadata modal 2025-02-13 15:34:09 +08:00
27 changed files with 1457 additions and 13 deletions

View File

@@ -20,7 +20,9 @@ import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
// import DatasetMetadataDrawer from '@/app/components/datasets/metadata/dataset-metadata-drawer'
// import MetaDataDocument from '@/app/components/datasets/metadata/metadata-document'
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
// Services
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
@@ -29,6 +31,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()
@@ -81,8 +84,54 @@ const Container = () => {
return router.replace('/apps')
}, [currentWorkspace, router])
const [isBuiltInEnabled, setIsBuiltInEnabled] = useState(false)
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='flex justify-end mt-[300px] mr-[100px]'>
{/* <MetaDataDocument /> */}
{/* <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={isBuiltInEnabled}
onIsBuiltInEnabledChange={setIsBuiltInEnabled}
onClose={() => { }}
/> */}
<EditMetadataBatchModal
documentNum={20}
list={[
{
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
},
{
id: '2', name: 'name2', type: DataType.number, value: 'ccc', isMultipleValue: true, isUpdated: true,
},
{
id: '2.1', name: 'num v', type: DataType.number, value: 10,
},
{
id: '3', name: 'name3', type: DataType.time, value: '', isUpdated: true, // updateType: UpdateType.delete,
},
]}
onHide={() => { }}
onChange={(list, newList, isApplyToAllSelectDocument) => { console.log(list, newList, isApplyToAllSelectDocument) }}
/>
</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
value={activeTab}

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 cursor-pointer' onClick={onClose} />
</Dialog.Title>}
</div>
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
{children}
</>

View File

@@ -12,10 +12,13 @@ export type InputNumberProps = {
max?: number
min?: number
defaultValue?: number
wrapClassName?: string
controlWrapClassName?: string
controlClassName?: string
} & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'>
export const InputNumber: FC<InputNumberProps> = (props) => {
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, ...rest } = props
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, wrapClassName, controlWrapClassName, controlClassName, ...rest } = props
const isValidValue = (v: number) => {
if (max && v > max)
@@ -46,7 +49,7 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
onChange(newValue)
}
return <div className='flex'>
return <div className={classNames('flex', wrapClassName)}>
<Input {...rest}
// disable default controller
type='text'
@@ -68,16 +71,18 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
}}
unit={unit}
/>
<div className='flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs'>
<div className={classNames('flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs', controlWrapClassName)}>
<button onClick={inc} className={classNames(
size === 'sm' ? 'pt-1' : 'pt-1.5',
'px-1.5 hover:bg-components-input-bg-hover',
controlClassName,
)}>
<RiArrowUpSLine className='size-3' />
</button>
<button onClick={dec} className={classNames(
size === 'sm' ? 'pb-1' : 'pb-1.5',
'px-1.5 hover:bg-components-input-bg-hover',
controlClassName,
)}>
<RiArrowDownSLine className='size-3' />
</button>

View File

@@ -0,0 +1,58 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
import Button from '../button'
import { RiCloseLine } from '@remixicon/react'
type Props = {
title: string
className?: string
beforeHeader?: React.ReactNode
onClose: () => void
hideCloseBtn?: boolean
onConfirm: () => void
children: React.ReactNode
}
const ModalLikeWrap: FC<Props> = ({
title,
className,
beforeHeader,
children,
onClose,
hideCloseBtn,
onConfirm,
}) => {
const { t } = useTranslation()
return (
<div className={cn('w-[320px] px-3 pt-3.5 pb-4 bg-components-panel-bg shadow-xl rounded-2xl border-[0.5px] border-components-panel-border', className)}>
{beforeHeader || null}
<div className='mb-1 flex h-6 items-center justify-between'>
<div className='system-xl-semibold text-text-primary'>{title}</div>
{!hideCloseBtn && (
<div
className='p-1.5 text-text-tertiary cursor-pointer'
onClick={onClose}
>
<RiCloseLine className='size-4' />
</div>
)}
</div>
<div className='mt-2'>{children}</div>
<div className='mt-4 flex justify-end'>
<Button
className='mr-2'
onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button
onClick={onConfirm}
variant='primary'
>{t('common.operation.save')}</Button>
</div>
</div>
)
}
export default React.memo(ModalLikeWrap)

View File

@@ -0,0 +1,31 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import Button from '../../base/button'
import { RiAddLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
type Props = {
className?: string
onClick?: () => void
}
const AddedMetadataButton: FC<Props> = ({
className,
onClick,
}) => {
const { t } = useTranslation()
return (
<Button
className={cn('w-full flex items-center', className)}
size='small'
variant='tertiary'
onClick={onClick}
>
<RiAddLine className='mr-1 size-3.5' />
<div>{t('dataset.metadata.addMetadata')}</div>
</Button>
)
}
export default React.memo(AddedMetadataButton)

View File

@@ -0,0 +1,87 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { DataType } from './types'
import ModalLikeWrap from '../../base/modal-like-wrap'
import Field from './field'
import OptionCard from '../../workflow/nodes/_base/components/option-card'
import Input from '@/app/components/base/input'
import { RiArrowLeftLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
const i18nPrefix = 'dataset.metadata.createMetadata'
export type Props = {
onSave: (data: any) => void
hasBack?: boolean
onBack?: () => void
}
const CreateContent: FC<Props> = ({
hasBack,
onBack,
onSave,
}) => {
const { t } = useTranslation()
const [type, setType] = useState(DataType.string)
const handleTypeChange = useCallback((newType: DataType) => {
return () => setType(newType)
}, [setType])
const [name, setName] = useState('')
const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value)
}, [setName])
const handleSave = useCallback(() => {
onSave({
type,
name,
})
}, [onSave, type, name])
return (
<ModalLikeWrap
title={t(`${i18nPrefix}.title`)}
onClose={() => { }}
onConfirm={handleSave}
hideCloseBtn={hasBack}
beforeHeader={hasBack && (
<div className='relative left-[-4px] mb-1 flex items-center py-1 space-x-1 text-text-accent cursor-pointer' onClick={onBack}>
<RiArrowLeftLine className='size-4' />
<div className='system-xs-semibold-uppercase'>{t(`${i18nPrefix}.back`)}</div>
</div>
)}
>
<div className='space-y-3'>
<Field label={t(`${i18nPrefix}.type`)}>
<div className='grid grid-cols-3 gap-2'>
<OptionCard
title='String'
selected={type === DataType.string}
onSelect={handleTypeChange(DataType.string)}
/>
<OptionCard
title='Number'
selected={type === DataType.number}
onSelect={handleTypeChange(DataType.number)}
/>
<OptionCard
title='Time'
selected={type === DataType.time}
onSelect={handleTypeChange(DataType.time)}
/>
</div>
</Field>
<Field label={t(`${i18nPrefix}.name`)}>
<Input
value={name}
onChange={handleNameChange}
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
/>
</Field>
</div>
</ModalLikeWrap>
)
}
export default React.memo(CreateContent)

View File

@@ -0,0 +1,43 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import type { Props as CreateContentProps } from './create-content'
import CreateContent from './create-content'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
type Props = {
onSave: (data: any) => void
trigger: React.ReactNode
popupLeft?: number
} & CreateContentProps
const CreateMetadataModal: FC<Props> = ({
trigger,
popupLeft = 20,
...createContentProps
}) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='left-start'
offset={{
mainAxis: popupLeft,
crossAxis: -38,
}}
>
<PortalToFollowElemTrigger
onClick={() => setOpen(!open)}
>
{trigger}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<CreateContent {...createContentProps} />
</PortalToFollowElemContent>
</PortalToFollowElem >
)
}
export default React.memo(CreateMetadataModal)

View File

@@ -0,0 +1,228 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useRef, 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'
import { useBoolean, useHover } from 'ahooks'
import Confirm from '@/app/components/base/confirm'
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
disabled?: boolean
payload: MetadataItemWithValueLength
onRename?: () => void
onDelete?: () => void
}
const Item: FC<ItemProps> = ({
readonly,
disabled,
payload,
onRename,
onDelete,
}) => {
const { t } = useTranslation()
const Icon = getIcon(payload.type)
const handleRename = useCallback(() => {
onRename?.()
}, [onRename])
const deleteBtnRef = useRef<HTMLDivElement>(null)
const isDeleteHovering = useHover(deleteBtnRef)
const [isShowDeleteConfirm, {
setTrue: showDeleteConfirm,
setFalse: hideDeleteConfirm,
}] = useBoolean(false)
const handleDelete = useCallback(() => {
hideDeleteConfirm()
onDelete?.()
}, [hideDeleteConfirm, onDelete])
return (
<div
key={payload.id}
className={cn(
!readonly && !disabled && 'group/item hover:shadow-xs cursor-pointer',
'border border-components-panel-border-subtle rounded-md bg-components-panel-on-panel-item-bg',
isDeleteHovering && 'border border-state-destructive-border bg-state-destructive-hover',
)}
>
<div
className={cn(
'flex items-center h-8 px-2 justify-between',
disabled && 'opacity-30', // not include border and bg
)}
>
<div className='flex items-center h-full text-text-tertiary space-x-1'>
<Icon className='shrink-0 size-4' />
<div className='max-w-[250px] truncate system-sm-medium text-text-primary'>{payload.name}</div>
<div className='shrink-0 system-xs-regular'>{payload.type}</div>
</div>
<div className='group-hover/item:hidden ml-2 shrink-0 system-xs-regular text-text-tertiary'>
{disabled ? t(`${i18nPrefix}.disabled`) : t(`${i18nPrefix}.values`, { num: payload.valueLength || 0 })}
</div>
<div className='group-hover/item:flex hidden ml-2 items-center text-text-tertiary space-x-1'>
<RiEditLine className='size-4 cursor-pointer' onClick={handleRename} />
<div ref={deleteBtnRef} className='hover:text-text-destructive'>
<RiDeleteBinLine className='size-4 cursor-pointer' onClick={showDeleteConfirm} />
</div>
</div>
{isShowDeleteConfirm && (
<Confirm
isShow
type='warning'
title={'Confirm to delete'}
content={`Are you sure you want to delete the metadata "${payload.name}"?`}
onConfirm={handleDelete}
onCancel={hideDeleteConfirm}
/>
)}
</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={t('dataset.metadata.metadata')}
footer={null}
panelClassname='px-4 block !max-w-[420px] my-2 rounded-l-2xl'
>
<div className='system-sm-regular text-text-tertiary'>{t(`${i18nPrefix}.description`)}</div>
<CreateModal trigger={<Button variant='primary' className='mt-3'>
<RiAddLine className='mr-1' />
{t(`${i18nPrefix}.addMetaData`)}
</Button>} hasBack onSave={handleAdd} />
<div className='mt-3 space-y-1'>
{userMetadata.map(payload => (
<Item
key={payload.id}
payload={payload}
onRename={handleRename(payload)}
onDelete={handleDelete(payload)}
/>
))}
</div>
<div className='mt-3 flex h-6 items-center'>
<Switch
defaultValue={isBuiltInEnabled}
onChange={onIsBuiltInEnabledChange}
/>
<div className='ml-2 mr-0.5 system-sm-semibold text-text-secondary'>{t(`${i18nPrefix}.builtIn`)}</div>
<Tooltip popupContent={<div className='max-w-[100px]'>{t(`${i18nPrefix}.builtInDescription`)}</div>} />
</div>
<div className='mt-1 space-y-1'>
{builtInMetadata.map(payload => (
<Item
key={payload.id}
readonly
disabled={!isBuiltInEnabled}
payload={payload}
/>
))}
</div>
{isShowRenameModal && (
<Modal isShow title={t(`${i18nPrefix}.rename`)} onClose={() => setIsShowRenameModal(false)}>
<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

@@ -0,0 +1,45 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { MetadataItemWithEdit } from '../types'
import cn from '@/utils/classnames'
import Label from './label'
import InputCombined from './input-combined'
import { RiIndeterminateCircleLine } from '@remixicon/react'
type Props = {
className?: string
payload: MetadataItemWithEdit
onChange: (value: any) => void
onRemove: () => void
}
const AddRow: FC<Props> = ({
className,
payload,
onChange,
onRemove,
}) => {
return (
<div className={cn('flex h-6 items-center space-x-0.5', className)}>
<Label text={payload.name} />
<InputCombined
type={payload.type}
value={payload.value}
onChange={onChange}
/>
<div
className={
cn(
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
)
}
onClick={onRemove}
>
<RiIndeterminateCircleLine className='size-4' />
</div>
</div>
)
}
export default React.memo(AddRow)

View File

@@ -0,0 +1,50 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { type MetadataItemWithEdit, UpdateType } from '../types'
import Label from './label'
import { RiDeleteBinLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import InputHasSetMultipleValue from './input-has-set-multiple-value'
import InputCombined from './input-combined'
import EditedBeacon from './edited-beacon'
type Props = {
payload: MetadataItemWithEdit
onChange: (payload: MetadataItemWithEdit) => void
onRemove: (id: string) => void
}
const EditMetadatabatchItem: FC<Props> = ({
payload,
onChange,
onRemove,
}) => {
const isUpdated = payload.isUpdated
const isDeleted = payload.updateType === UpdateType.delete
return (
<div className='flex h-6 items-center space-x-0.5'>
{isUpdated ? <EditedBeacon onReset={() => { }} /> : <div className='shrink-0 size-4' />}
<Label text={payload.name} isDeleted={isDeleted} />
{payload.isMultipleValue
? <InputHasSetMultipleValue onClear={() => onChange({ ...payload, isMultipleValue: false })} />
: <InputCombined
type={payload.type}
value={payload.value}
onChange={v => onChange({ ...payload, value: v as string })
} />}
<div
className={
cn(
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
isDeleted && 'cursor-default bg-state-destructive-hover text-text-destructive')
}
onClick={() => onRemove(payload.id)}
>
<RiDeleteBinLine className='size-4' />
</div>
</div>
)
}
export default React.memo(EditMetadatabatchItem)

View File

@@ -0,0 +1,36 @@
'use client'
import type { FC } from 'react'
import React, { useRef } from 'react'
import { useHover } from 'ahooks'
import { RiResetLeftLine } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import { useTranslation } from 'react-i18next'
type Props = {
onReset: () => void
}
const EditedBeacon: FC<Props> = ({
onReset,
}) => {
const { t } = useTranslation()
const ref = useRef(null)
const isHovering = useHover(ref)
return (
<div ref={ref} className='size-4 cursor-pointer'>
{isHovering ? (
<Tooltip popupContent={t('common.operation.reset')}>
<div className='flex justify-center items-center size-4 bg-text-accent-secondary rounded-full' onClick={onReset}>
<RiResetLeftLine className='size-[10px] text-text-primary-on-surface' />
</div>
</Tooltip>
) : (
<div className='flex items-center justify-center size-4'>
<div className='size-1 rounded-full bg-text-accent-secondary'></div>
</div>
)}
</div>
)
}
export default React.memo(EditedBeacon)

View File

@@ -0,0 +1,51 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { DataType } from '../types'
import Input from '@/app/components/base/input'
import { InputNumber } from '@/app/components/base/input-number'
import cn from '@/utils/classnames'
type Props = {
className?: string
type: DataType
value: any
onChange: (value: any) => void
}
const InputCombined: FC<Props> = ({
className: configClassName,
type,
value,
onChange,
}) => {
// TODO: configClassName...
const className = cn('grow p-0.5 h-6 text-xs')
if (type === DataType.time)
return <div className='grow text-xs'>Datepicker placeholder</div>
if (type === DataType.number) {
return (
<div className='grow text-[0]'>
<InputNumber
className={cn(className, 'rounded-l-md')}
value={value}
onChange={onChange}
size='sm'
controlWrapClassName='overflow-hidden'
controlClassName='pt-0 pb-0'
/>
</div>
)
}
return (
<Input
wrapperClassName={configClassName}
className={cn(className, 'rounded-md')}
value={value}
onChange={e => onChange(e.target.value)}
>
</Input>
)
}
export default React.memo(InputCombined)

View File

@@ -0,0 +1,29 @@
'use client'
import { RiCloseLine } from '@remixicon/react'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
onClear: () => void
}
const InputHasSetMultipleValue: FC<Props> = ({
onClear,
}) => {
const { t } = useTranslation()
return (
<div className='grow h-6 p-0.5 rounded-md bg-components-input-bg-normal text-[0]'>
<div className='inline-flex rounded-[5px] items-center h-5 pl-1.5 pr-0.5 bg-components-badge-white-to-dark border-[0.5px] border-components-panel-border shadow-xs space-x-0.5'>
<div className='system-xs-regular text-text-secondary'>{t('dataset.metadata.batchEditMetadata.multipleValue')}</div>
<div className='p-0.5rounded-sm text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer'>
<RiCloseLine
className='size-3.5 '
onClick={onClear}
/>
</div>
</div>
</div>
)
}
export default React.memo(InputHasSetMultipleValue)

View File

@@ -0,0 +1,27 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
type Props = {
isDeleted?: boolean,
className?: string,
text: string
}
const Label: FC<Props> = ({
isDeleted,
className,
text,
}) => {
return (
<div className={cn(
'shrink-0 w-[136px] system-xs-medium text-text-tertiary truncate',
isDeleted && 'line-through text-text-quaternary',
className,
)}>
{text}
</div>
)
}
export default React.memo(Label)

View File

@@ -0,0 +1,139 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import Modal from '../../../base/modal'
import { DataType, type MetadataItemWithEdit } from '../types'
import EditMetadataBatchItem from './edit-row'
import AddedMetadataItem from './add-row'
import Button from '../../../base/button'
import { useTranslation } from 'react-i18next'
import Checkbox from '../../../base/checkbox'
import Tooltip from '../../../base/tooltip'
import SelectMetadataModal from '../select-metadata-modal'
import { RiQuestionLine } from '@remixicon/react'
import Divider from '@/app/components/base/divider'
import AddMetadataButton from '../add-metadata-button'
const i18nPrefix = 'dataset.metadata.batchEditMetadata'
type Props = {
documentNum: number
list: MetadataItemWithEdit[]
onChange: (list: MetadataItemWithEdit[], addedList: MetadataItemWithEdit[], isApplyToAllSelectDocument: boolean) => void
onHide: () => void
}
const EditMetadataBatchModal: FC<Props> = ({
documentNum,
list,
onChange,
onHide,
}) => {
const { t } = useTranslation()
const [templeList, setTempleList] = useState<MetadataItemWithEdit[]>(list)
const handleTemplesChange = useCallback((payload: MetadataItemWithEdit) => {
const newTempleList = templeList.map(i => i.id === payload.id ? payload : i)
setTempleList(newTempleList)
}, [templeList])
const handleTempleItemRemove = useCallback((id: string) => {
const newTempleList = templeList.filter(i => i.id !== id)
setTempleList(newTempleList)
}, [templeList])
const testAddedList: MetadataItemWithEdit[] = [
{
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
},
{
id: '2.1', name: 'num v', type: DataType.number, value: 10,
},
]
const [addedList, setAddedList] = useState<MetadataItemWithEdit[]>(testAddedList)
const handleAddedListChange = useCallback((payload: MetadataItemWithEdit) => {
const newAddedList = addedList.map(i => i.id === payload.id ? payload : i)
setAddedList(newAddedList)
}, [addedList])
const handleAddedItemRemove = useCallback((removeIndex: number) => {
return () => {
const newAddedList = addedList.filter((i, index) => index !== removeIndex)
setAddedList(newAddedList)
}
}, [addedList])
const [isApplyToAllSelectDocument, setIsApplyToAllSelectDocument] = useState(false)
const handleSave = useCallback(() => {
onChange(templeList, addedList, isApplyToAllSelectDocument)
}, [templeList, addedList, isApplyToAllSelectDocument, onChange])
return (
<Modal
title={t(`${i18nPrefix}.editMetadata`)}
isShow
closable
onClose={onHide}
className='!max-w-[640px]'
>
<div className='system-xs-medium text-text-accent'>{t(`${i18nPrefix}.editDocumentsNum`, { num: documentNum })}</div>
{/* TODO handle list scroll. There is two list. */}
<div className='mt-4 space-y-2'>
{templeList.map(item => (
<EditMetadataBatchItem
key={item.id}
payload={item}
onChange={handleTemplesChange}
onRemove={handleTempleItemRemove}
/>
))}
</div>
<div className='mt-4 pl-[18px]'>
<div className='flex items-center'>
<div className='mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary'>{t('dataset.metadata.createMetadata.title')}</div>
<Divider bgStyle='gradient' />
</div>
<div className='mt-2 space-y-2'>
{addedList.map((item, i) => (
<AddedMetadataItem
key={i}
payload={item}
onChange={handleAddedListChange}
onRemove={handleAddedItemRemove(i)}
/>
))}
</div>
<div className='mt-3'>
<SelectMetadataModal
popupPlacement='top-start'
popupOffset={{ mainAxis: 4, crossAxis: 0 }}
trigger={
<AddMetadataButton />
}
onSave={data => setAddedList([...addedList, data])}
/>
</div>
</div>
<div className='mt-4 flex items-center justify-between'>
<div className='flex items-center select-none'>
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} />
<div className='ml-2 mr-1 system-xs-medium text-text-secondary'>{t(`${i18nPrefix}.applyToAllSelectDocument`)}</div>
<Tooltip popupContent={
<div className='max-w-[240px]'>{t(`${i18nPrefix}.applyToAllSelectDocumentTip`)}</div>
} >
<div className='p-px cursor-pointer'>
<RiQuestionLine className='size-3.5 text-text-tertiary' />
</div>
</Tooltip>
</div>
<div className='flex items-center space-x-2'>
<Button
onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button
onClick={handleSave}
variant='primary'
>{t('common.operation.save')}</Button>
</div>
</div>
</Modal>
)
}
export default React.memo(EditMetadataBatchModal)

View File

@@ -0,0 +1,23 @@
'use client'
import type { FC } from 'react'
import React from 'react'
type Props = {
className?: string
label: string
children: React.ReactNode
}
const Field: FC<Props> = ({
className,
label,
children,
}) => {
return (
<div className={className}>
<div className='py-1 system-sm-semibold text-text-secondary'>{label}</div>
<div className='mt-1'>{children}</div>
</div>
)
}
export default React.memo(Field)

View File

@@ -0,0 +1,26 @@
'use client'
import type { FC } from 'react'
import React from 'react'
type Props = {
label: string
children: React.ReactNode
}
const Field: FC<Props> = ({
label,
children,
}) => {
return (
<div className='flex items-start space-x-2'>
<div className='shrink-0 w-[128px] truncate py-1 items-center text-text-tertiary system-xs-medium'>
{label}
</div>
<div className='shrink-0 w-[244px]'>
{children}
</div>
</div>
)
}
export default React.memo(Field)

View File

@@ -0,0 +1,127 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { DataType, type MetadataItemWithValue } from '../types'
import InfoGroup from './info-group'
import NoData from './no-data'
import Button from '@/app/components/base/button'
import { RiEditLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
const i18nPrefix = 'dataset.metadata.documentMetadata'
const MetadataDocument: FC = () => {
const { t } = useTranslation()
const [isEdit, setIsEdit] = useState(true)
const [list, setList] = useState<MetadataItemWithValue[]>([
{
id: '1',
name: 'Doc type',
value: 'PDF',
type: DataType.string,
},
{
id: '2',
name: 'Title',
value: 'PDF',
type: DataType.string,
},
])
const [tempList, setTempList] = useState<MetadataItemWithValue[]>(list)
const builtInEnabled = true
const builtList = [
{
id: '1',
name: 'OriginalfileNmae',
value: 'Steve Jobs The Man Who Thought Different.pdf',
type: DataType.string,
},
{
id: '2',
name: 'Title',
value: 'PDF',
type: DataType.string,
},
]
const hasData = list.length > 0
const documentInfoList = builtList
const technicalParams = builtList
return (
<div className='w-[388px] space-y-4'>
{!hasData ? (
<div className='pl-2'>
<InfoGroup
title={t('dataset.metadata.metadata')}
uppercaseTitle={false}
titleTooltip={t(`${i18nPrefix}.metadataToolTip`)}
list={isEdit ? tempList : list}
headerRight={isEdit ? (
<div className='flex space-x-1'>
<Button variant='ghost' size='small' onClick={() => {
setTempList(list)
setIsEdit(false)
}}>
<div>{t('common.operation.cancel')}</div>
</Button>
<Button variant='primary' size='small' onClick={() => {
setIsEdit(false)
setList(tempList)
}}>
<div>{t('common.operation.save')}</div>
</Button>
</div>
) : (
<Button variant='ghost' size='small' onClick={() => {
setTempList(list)
setIsEdit(true)
}}>
<RiEditLine className='mr-1 size-3.5 text-text-tertiary cursor-pointer' />
<div>{t('common.operation.edit')}</div>
</Button>
)}
isEdit={isEdit}
contentClassName='mt-5'
onChange={(item) => {
const newList = tempList.map(i => (i.name === item.name ? item : i))
setList(newList)
}}
onDelete={(item) => {
const newList = tempList.filter(i => i.name !== item.name)
setList(newList)
}}
onAdd={() => {
}}
/>
{builtInEnabled && (
<>
<Divider className='my-3' bgStyle='gradient' />
<InfoGroup
noHeader
titleTooltip='Built-in metadata is system-generated metadata that is automatically added to the document. You can enable or disable built-in metadata here.'
list={builtList}
/>
</>
)}
</div>
) : (
<NoData onStart={() => { }} />
)}
<InfoGroup
className='pl-2'
title={t(`${i18nPrefix}.documentInformation`)}
list={documentInfoList}
/>
<InfoGroup
className='pl-2'
title={t(`${i18nPrefix}.technicalParameters`)}
list={technicalParams}
/>
</div>
)
}
export default React.memo(MetadataDocument)

View File

@@ -0,0 +1,94 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { MetadataItemWithValue } from '../types'
import Field from './field'
import InputCombined from '../edit-metadata-batch/input-combined'
import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import cn from '@/utils/classnames'
import Divider from '@/app/components/base/divider'
import SelectMetadataModal from '../select-metadata-modal'
import AddMetadataButton from '../add-metadata-button'
type Props = {
className?: string
noHeader?: boolean
title?: string
uppercaseTitle?: boolean
titleTooltip?: string
headerRight?: React.ReactNode
contentClassName?: string
list: MetadataItemWithValue[]
isEdit?: boolean
onChange?: (item: MetadataItemWithValue) => void
onDelete?: (item: MetadataItemWithValue) => void
onAdd?: (item: MetadataItemWithValue) => void
}
const InfoGroup: FC<Props> = ({
className,
noHeader,
title,
uppercaseTitle = true,
titleTooltip,
headerRight,
contentClassName,
list,
isEdit,
onChange,
onDelete,
onAdd,
}) => {
return (
<div className={cn('bg-white', className)}>
{!noHeader && (
<div className='flex items-center justify-between'>
<div className='flex items-center space-x-1'>
<div className={cn('text-text-secondary', uppercaseTitle ? 'system-xs-semibold-uppercase' : 'system-md-semibold')}>{title}</div>
{titleTooltip && (
<Tooltip popupContent={<div className='max-w-[240px]'>{titleTooltip}</div>}>
<RiQuestionLine className='size-3.5 text-text-tertiary' />
</Tooltip>
)}
</div>
{headerRight}
{/* <div className='flex px-1.5 rounded-md hover:bg-components-button-tertiary-bg-hover items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsEdit(true)}>
</div> */}
</div>
)}
<div className={cn('mt-3 space-y-1', !noHeader && 'mt-0', contentClassName)}>
{isEdit && (
<div>
<SelectMetadataModal
trigger={
<AddMetadataButton />
}
onSave={() => { }}
/>
<Divider className='my-3 ' bgStyle='gradient' />
</div>
)}
{list.map((item, i) => (
<Field key={item.id || `${i}`} label={item.name}>
{isEdit ? (
<div className='flex items-center space-x-0.5'>
<InputCombined
className='h-6'
type={item.type}
value={item.value}
onChange={value => onChange?.({ ...item, value })}
/>
<div className='shrink-0 p-1 rounded-md text-text-tertiary hover:text-text-destructive hover:bg-state-destructive-hover cursor-pointer'>
<RiDeleteBinLine className='size-4' />
</div>
</div>
) : (<div className='py-1 system-xs-regular text-text-secondary'>{item.value}</div>)}
</Field>
))}
</div>
</div>
)
}
export default React.memo(InfoGroup)

View File

@@ -0,0 +1,27 @@
'use client'
import Button from '@/app/components/base/button'
import { RiArrowRightLine } from '@remixicon/react'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
onStart: () => void
}
const NoData: FC<Props> = ({
onStart,
}) => {
const { t } = useTranslation()
return (
<div className='p-4 pt-3 rounded-xl bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2'>
<div className='text-text-secondary text-xs font-semibold leading-5'>{t('dataset.metadata.metadata')}</div>
<div className='mt-1 system-xs-regular text-text-tertiary'>{t('dataset.metadata.documentMetadata.metadataToolTip')}</div>
<Button variant='primary' className='mt-2' onClick={onStart}>
<div>{t('dataset.metadata.documentMetadata.startLabeling')}</div>
<RiArrowRightLine className='ml-1 size-4' />
</Button>
</div>
)
}
export default React.memo(NoData)

View File

@@ -0,0 +1,76 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import type { Props as CreateContentProps } from './create-content'
import CreateContent from './create-content'
import SelectMetadata from './select-metadata'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
import type { MetadataItem } from './types'
import type { Placement } from '@floating-ui/react'
import { DataType } from './types'
type Props = {
popupPlacement?: Placement
popupOffset?: { mainAxis: number, crossAxis: number }
onSave: (data: any) => void
trigger: React.ReactNode
} & CreateContentProps
enum Step {
select = 'select',
create = 'create',
}
const testMetadataList: MetadataItem[] = [
{ id: '1', name: 'name1', type: DataType.string },
{ id: '2', name: 'name2', type: DataType.number },
{ id: '3', name: 'name3', type: DataType.time },
]
const SelectMetadataModal: FC<Props> = ({
popupPlacement = 'left-start',
popupOffset = { mainAxis: -38, crossAxis: 4 },
trigger,
onSave,
}) => {
const [open, setOpen] = useState(false)
const [step, setStep] = useState(Step.select)
const handleSave = useCallback((data: MetadataItem) => {
onSave(data)
setOpen(false)
}, [onSave])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement={popupPlacement}
offset={popupOffset}
>
<PortalToFollowElemTrigger
onClick={() => setOpen(!open)}
className='block'
>
{trigger}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
{step === Step.select ? (
<SelectMetadata
onSelect={handleSave}
list={testMetadataList}
onNew={() => setStep(Step.create)}
onManage={() => { }}
/>
) : (
<CreateContent
onSave={handleSave}
hasBack
onBack={() => setStep(Step.select)}
/>
)}
</PortalToFollowElemContent>
</PortalToFollowElem >
)
}
export default React.memo(SelectMetadataModal)

View File

@@ -0,0 +1,72 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import type { MetadataItem } from './types'
import SearchInput from '../../base/search-input'
import { RiAddLine, RiArrowRightUpLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { getIcon } from './utils/get-icon'
const i18nPrefix = 'dataset.metadata.selectMetadata'
type Props = {
list: MetadataItem[]
onSelect: (data: any) => void
onNew: () => void
onManage: () => void
}
const SelectMetadata: FC<Props> = ({
list,
onSelect,
onNew,
onManage,
}) => {
const { t } = useTranslation()
const [query, setQuery] = useState('')
return (
<div className='w-[320px] pt-2 pb-0 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg backdrop-blur-[5px]'>
<SearchInput
className='mx-2'
value={query}
onChange={setQuery}
placeholder={t(`${i18nPrefix}.search`)}
/>
<div className='mt-2'>
{list.map((item) => {
const Icon = getIcon(item.type)
return (
<div
key={item.id}
className='mx-1 flex items-center h-6 px-3 justify-between rounded-md hover:bg-state-base-hover cursor-pointer'
onClick={() => onSelect(item)}
>
<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'>{item.name}</div>
</div>
<div className='ml-1 shrink-0 system-xs-regular text-text-tertiary'>
{item.type}
</div>
</div>
)
})}
</div>
<div className='mt-1 flex justify-between p-1 border-t border-divider-subtle'>
<div className='flex items-center h-6 px-3 text-text-secondary rounded-md hover:bg-state-base-hover cursor-pointer space-x-1' onClick={onNew}>
<RiAddLine className='size-3.5' />
<div className='system-sm-medium'>{t(`${i18nPrefix}.newAction`)}</div>
</div>
<div className='flex items-center h-6 text-text-secondary '>
<div className='mr-[3px] w-px h-3 bg-divider-regular'></div>
<div className='flex h-full items-center px-1.5 hover:bg-state-base-hover rounded-md cursor-pointer' onClick={onManage}>
<div className='mr-1 system-sm-medium'>{t(`${i18nPrefix}.manageAction`)}</div>
<RiArrowRightUpLine className='size-3.5' />
</div>
</div>
</div>
</div>
)
}
export default React.memo(SelectMetadata)

View File

@@ -0,0 +1,28 @@
export enum DataType {
string = 'string',
number = 'number',
time = 'time',
}
export type MetadataItem = {
id: string
type: DataType
name: string
}
export type MetadataItemWithValue = MetadataItem & {
value: string | number
}
export type MetadataItemWithValueLength = MetadataItem & {
valueLength: number
}
export enum UpdateType {
changeValue = 'changeValue',
delete = 'delete',
}
export type MetadataItemWithEdit = MetadataItemWithValue & {
isMultipleValue?: boolean
isUpdated?: boolean
updateType?: UpdateType
}

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)
}

View File

@@ -168,6 +168,46 @@ const translation = {
preprocessDocument: '{{num}} Preprocess Documents',
allKnowledge: 'All Knowledge',
allKnowledgeDescription: 'Select to display all knowledge in this workspace. Only the Workspace Owner can manage all knowledge.',
metadata: {
metadata: 'Metadata',
addMetadata: 'Add Metadata',
createMetadata: {
title: 'New Metadata',
back: 'Back',
type: 'Type',
name: 'Name',
namePlaceholder: 'Add metadata name',
},
batchEditMetadata: {
editMetadata: 'Edit Metadata',
editDocumentsNum: 'Editing {{num}} documents',
applyToAllSelectDocument: 'Apply to all selected documents',
applyToAllSelectDocumentTip: 'Automatically create all the above edited and new metadata for all selected documents, otherwise editing metadata will only apply to documents with it.',
multipleValue: 'Multiple Value',
},
selectMetadata: {
search: 'Search metadata',
newAction: 'New Metadata',
manageAction: 'Manage',
},
datasetMetadata: {
description: 'You can manage all metadata in this knowledge here. Modifications will be synchronized to every document.',
addMetaData: 'Add Metadata',
values: '{{num}} Values',
disabled: 'Disabled',
rename: 'Rename',
name: 'Name',
namePlaceholder: 'Metadata name',
builtIn: 'Built-in',
builtInDescription: 'Built-in metadata is automatically extracted and generated. It must be enabled before use and cannot be edited.',
},
documentMetadata: {
metadataToolTip: 'Metadata serves as a critical filter that enhances the accuracy and relevance of information retrieval. You can modify and add metadata for this document here.',
startLabeling: 'Start Labeling',
documentInformation: 'Document Information',
technicalParameters: 'Technical Parameters',
},
},
}
export default translation

View File

@@ -168,6 +168,46 @@ const translation = {
preprocessDocument: '{{num}} 个预处理文档',
allKnowledge: '所有知识库',
allKnowledgeDescription: '选择以显示该工作区内所有知识库。只有工作区所有者才能管理所有知识库。',
metadata: {
metadata: '元数据',
addMetadata: '添加元数据',
createMetadata: {
title: '新建元数据',
back: '返回',
type: '类型',
name: '名称',
namePlaceholder: '添加元数据名称',
},
batchEditMetadata: {
editMetadata: '编辑元数据',
editDocumentsNum: '编辑 {{num}} 个文档',
applyToAllSelectDocument: '应用于所有选定文档',
applyToAllSelectDocumentTip: '自动为所有选定文档创建上述编辑和新元数据,否则仅对具有元数据的文档应用编辑。',
multipleValue: '多个值',
},
selectMetadata: {
search: '搜索元数据',
newAction: '新建元数据',
manageAction: '管理',
},
datasetMetadata: {
description: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
addMetaData: '添加元数据',
values: '{{num}} 个值',
disabled: '已禁用',
rename: '重命名',
name: '名称',
namePlaceholder: '元数据名称',
builtIn: '内置',
builtInDescription: '内置元数据是系统预定义的元数据,您可以在此处查看和管理内置元数据。',
},
documentMetadata: {
metadataToolTip: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
startLabeling: '开始标注',
documentInformation: '文档信息',
technicalParameters: '技术参数',
},
},
}
export default translation