dify/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

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)