mirror of
https://github.com/langgenius/dify.git
synced 2026-07-26 14:18:35 +08:00
218 lines
7.7 KiB
TypeScript
218 lines
7.7 KiB
TypeScript
'use client'
|
|
import type { FC } from 'react'
|
|
import type { BuiltInMetadataItem, MetadataItemInBatchEdit, MetadataItemWithEdit } from '../types'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { produce } from 'immer'
|
|
import * as React from 'react'
|
|
import { useCallback, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import Divider from '@/app/components/base/divider'
|
|
import { useCreateMetaData } from '@/service/knowledge/use-metadata'
|
|
import { Infotip } from '../../../base/infotip'
|
|
import useCheckMetadataName from '../hooks/use-check-metadata-name'
|
|
import { DatasetMetadataPicker } from '../metadata-dataset/dataset-metadata-picker'
|
|
import { UpdateType } from '../types'
|
|
import AddedMetadataItem from './add-row'
|
|
import EditMetadataBatchItem from './edit-row'
|
|
|
|
const i18nPrefix = 'metadata.batchEditMetadata'
|
|
type Props = Readonly<{
|
|
datasetId: string
|
|
documentNum: number
|
|
list: MetadataItemInBatchEdit[]
|
|
onSave: (
|
|
editedList: MetadataItemInBatchEdit[],
|
|
addedList: MetadataItemInBatchEdit[],
|
|
isApplyToAllSelectDocument: boolean,
|
|
) => void
|
|
onHide: () => void
|
|
onShowManage: () => void
|
|
}>
|
|
const EditMetadataBatchModal: FC<Props> = ({
|
|
datasetId,
|
|
documentNum,
|
|
list,
|
|
onSave,
|
|
onHide,
|
|
onShowManage,
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const [templeList, setTempleList] = useState<MetadataItemWithEdit[]>(list)
|
|
const handleTemplesChange = useCallback(
|
|
(payload: MetadataItemWithEdit) => {
|
|
const newTempleList = produce(templeList, (draft) => {
|
|
const index = draft.findIndex((i) => i.id === payload.id)
|
|
if (index !== -1) {
|
|
draft[index] = payload
|
|
draft[index].isUpdated = true
|
|
draft[index].updateType = UpdateType.changeValue
|
|
}
|
|
})
|
|
setTempleList(newTempleList)
|
|
},
|
|
[templeList],
|
|
)
|
|
const handleTempleItemRemove = useCallback(
|
|
(id: string) => {
|
|
const newTempleList = produce(templeList, (draft) => {
|
|
const index = draft.findIndex((i) => i.id === id)
|
|
if (index !== -1) {
|
|
draft[index]!.isUpdated = true
|
|
draft[index]!.updateType = UpdateType.delete
|
|
}
|
|
})
|
|
setTempleList(newTempleList)
|
|
},
|
|
[templeList],
|
|
)
|
|
const handleItemReset = useCallback(
|
|
(id: string) => {
|
|
const newTempleList = produce(templeList, (draft) => {
|
|
const index = draft.findIndex((i) => i.id === id)
|
|
if (index !== -1) {
|
|
draft[index] = { ...list[index]! }
|
|
draft[index]!.isUpdated = false
|
|
delete draft[index]!.updateType
|
|
}
|
|
})
|
|
setTempleList(newTempleList)
|
|
},
|
|
[list, templeList],
|
|
)
|
|
const { checkName } = useCheckMetadataName()
|
|
const { mutate: doAddMetaData } = useCreateMetaData(datasetId)
|
|
const handleAddMetaData = useCallback(
|
|
async (payload: BuiltInMetadataItem) => {
|
|
const errorMsg = checkName(payload.name).errorMsg
|
|
if (errorMsg) {
|
|
toast.error(errorMsg)
|
|
return Promise.reject(new Error(errorMsg))
|
|
}
|
|
await doAddMetaData(payload)
|
|
toast.success(t(($) => $['api.actionSuccess'], { ns: 'common' }))
|
|
},
|
|
[checkName, doAddMetaData, t],
|
|
)
|
|
const [addedList, setAddedList] = useState<MetadataItemWithEdit[]>([])
|
|
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(() => {
|
|
onSave(
|
|
templeList.filter((item) => item.updateType !== UpdateType.delete),
|
|
addedList,
|
|
isApplyToAllSelectDocument,
|
|
)
|
|
}, [templeList, addedList, isApplyToAllSelectDocument, onSave])
|
|
return (
|
|
<Dialog
|
|
open
|
|
onOpenChange={(open) => {
|
|
if (!open) onHide()
|
|
}}
|
|
>
|
|
<DialogContent className="w-full max-w-[640px]! overflow-hidden! border-none text-left align-middle">
|
|
<DialogCloseButton />
|
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
|
{t(($) => $[`${i18nPrefix}.editMetadata`], { ns: 'dataset' })}
|
|
</DialogTitle>
|
|
|
|
<div className="mt-1 system-xs-medium text-text-accent">
|
|
{t(($) => $[`${i18nPrefix}.editDocumentsNum`], { ns: 'dataset', num: documentNum })}
|
|
</div>
|
|
<div className="max-h-[305px] overflow-x-hidden overflow-y-auto">
|
|
<div className="mt-4 space-y-2">
|
|
{templeList.map((item) => (
|
|
<EditMetadataBatchItem
|
|
key={item.id}
|
|
payload={item}
|
|
onChange={handleTemplesChange}
|
|
onRemove={handleTempleItemRemove}
|
|
onReset={handleItemReset}
|
|
/>
|
|
))}
|
|
</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(($) => $['metadata.createMetadata.title'], { ns: 'dataset' })}
|
|
</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">
|
|
<DatasetMetadataPicker
|
|
datasetId={datasetId}
|
|
placement="top-start"
|
|
sideOffset={4}
|
|
alignOffset={0}
|
|
onCreateMetadata={handleAddMetaData}
|
|
onSelectMetadata={(data) =>
|
|
setAddedList([...addedList, data as MetadataItemWithEdit])
|
|
}
|
|
onOpenMetadataManagement={onShowManage}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 flex items-center justify-between">
|
|
<div className="flex items-center select-none">
|
|
<label className="flex cursor-pointer items-center">
|
|
<Checkbox
|
|
checked={isApplyToAllSelectDocument}
|
|
onCheckedChange={setIsApplyToAllSelectDocument}
|
|
/>
|
|
<span className="mr-1 ml-2 system-xs-medium text-text-secondary">
|
|
{t(($) => $[`${i18nPrefix}.applyToAllSelectDocument`], { ns: 'dataset' })}
|
|
</span>
|
|
</label>
|
|
<Infotip
|
|
aria-label={t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], {
|
|
ns: 'dataset',
|
|
})}
|
|
className="p-px text-text-tertiary"
|
|
popupClassName="max-w-[240px]"
|
|
>
|
|
{t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], { ns: 'dataset' })}
|
|
</Infotip>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Button onClick={onHide}>{t(($) => $['operation.cancel'], { ns: 'common' })}</Button>
|
|
<Button onClick={handleSave} variant="primary">
|
|
{t(($) => $['operation.save'], { ns: 'common' })}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|
|
export default React.memo(EditMetadataBatchModal)
|