Revert "Feat/parent child retrieval" (#12095)

This commit is contained in:
-LAN-
2024-12-25 20:55:44 +08:00
committed by GitHub
parent 9231fdbf4c
commit db2aa83a7c
216 changed files with 3116 additions and 9066 deletions

View File

@@ -1,86 +0,0 @@
import React, { type FC, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useKeyPress } from 'ahooks'
import { useDocumentContext } from '../../index'
import Button from '@/app/components/base/button'
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
type IActionButtonsProps = {
handleCancel: () => void
handleSave: () => void
loading: boolean
actionType?: 'edit' | 'add'
handleRegeneration?: () => void
isChildChunk?: boolean
}
const ActionButtons: FC<IActionButtonsProps> = ({
handleCancel,
handleSave,
loading,
actionType = 'edit',
handleRegeneration,
isChildChunk = false,
}) => {
const { t } = useTranslation()
const mode = useDocumentContext(s => s.mode)
const parentMode = useDocumentContext(s => s.parentMode)
useKeyPress(['esc'], (e) => {
e.preventDefault()
handleCancel()
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
e.preventDefault()
if (loading)
return
handleSave()
}
, { exactMatch: true, useCapture: true })
const isParentChildParagraphMode = useMemo(() => {
return mode === 'hierarchical' && parentMode === 'paragraph'
}, [mode, parentMode])
return (
<div className='flex items-center gap-x-2'>
<Button
onClick={handleCancel}
>
<div className='flex items-center gap-x-1'>
<span className='text-components-button-secondary-text system-sm-medium'>{t('common.operation.cancel')}</span>
<span className='px-[1px] bg-components-kbd-bg-gray rounded-[4px] text-text-tertiary system-kbd'>ESC</span>
</div>
</Button>
{(isParentChildParagraphMode && actionType === 'edit' && !isChildChunk)
? <Button
onClick={handleRegeneration}
disabled={loading}
>
<span className='text-components-button-secondary-text system-sm-medium'>
{t('common.operation.saveAndRegenerate')}
</span>
</Button>
: null
}
<Button
variant='primary'
onClick={handleSave}
disabled={loading}
>
<div className='flex items-center gap-x-1'>
<span className='text-components-button-primary-text'>{t('common.operation.save')}</span>
<div className='flex items-center gap-x-0.5'>
<span className='w-4 h-4 bg-components-kbd-bg-white rounded-[4px] text-text-primary-on-surface system-kbd capitalize'>{getKeyboardKeyNameBySystem('ctrl')}</span>
<span className='w-4 h-4 bg-components-kbd-bg-white rounded-[4px] text-text-primary-on-surface system-kbd'>S</span>
</div>
</div>
</Button>
</div>
)
}
ActionButtons.displayName = 'ActionButtons'
export default React.memo(ActionButtons)

View File

@@ -1,32 +0,0 @@
import React, { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import classNames from '@/utils/classnames'
import Checkbox from '@/app/components/base/checkbox'
type AddAnotherProps = {
className?: string
isChecked: boolean
onCheck: () => void
}
const AddAnother: FC<AddAnotherProps> = ({
className,
isChecked,
onCheck,
}) => {
const { t } = useTranslation()
return (
<div className={classNames('flex items-center gap-x-1 pl-1', className)}>
<Checkbox
key='add-another-checkbox'
className='shrink-0'
checked={isChecked}
onCheck={onCheck}
/>
<span className='text-text-tertiary system-xs-medium'>{t('datasetDocuments.segment.addAnother')}</span>
</div>
)
}
export default React.memo(AddAnother)

View File

@@ -1,103 +0,0 @@
import React, { type FC } from 'react'
import { RiArchive2Line, RiCheckboxCircleLine, RiCloseCircleLine, RiDeleteBinLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import Divider from '@/app/components/base/divider'
import classNames from '@/utils/classnames'
import Confirm from '@/app/components/base/confirm'
const i18nPrefix = 'dataset.batchAction'
type IBatchActionProps = {
className?: string
selectedIds: string[]
onBatchEnable: () => void
onBatchDisable: () => void
onBatchDelete: () => Promise<void>
onArchive?: () => void
onCancel: () => void
}
const BatchAction: FC<IBatchActionProps> = ({
className,
selectedIds,
onBatchEnable,
onBatchDisable,
onArchive,
onBatchDelete,
onCancel,
}) => {
const { t } = useTranslation()
const [isShowDeleteConfirm, {
setTrue: showDeleteConfirm,
setFalse: hideDeleteConfirm,
}] = useBoolean(false)
const [isDeleting, {
setTrue: setIsDeleting,
}] = useBoolean(false)
const handleBatchDelete = async () => {
setIsDeleting()
await onBatchDelete()
hideDeleteConfirm()
}
return (
<div className={classNames('w-full flex justify-center gap-x-2', className)}>
<div className='flex items-center gap-x-1 p-1 rounded-[10px] bg-components-actionbar-bg-accent border border-components-actionbar-border-accent shadow-xl shadow-shadow-shadow-5 backdrop-blur-[5px]'>
<div className='inline-flex items-center gap-x-2 pl-2 pr-3 py-1'>
<span className='w-5 h-5 flex items-center justify-center px-1 py-0.5 bg-text-accent rounded-md text-text-primary-on-surface text-xs font-medium'>
{selectedIds.length}
</span>
<span className='text-text-accent text-[13px] font-semibold leading-[16px]'>{t(`${i18nPrefix}.selected`)}</span>
</div>
<Divider type='vertical' className='mx-0.5 h-3.5 bg-divider-regular' />
<div className='flex items-center gap-x-0.5 px-3 py-2'>
<RiCheckboxCircleLine className='w-4 h-4 text-components-button-ghost-text' />
<button type='button' className='px-0.5 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onBatchEnable}>
{t(`${i18nPrefix}.enable`)}
</button>
</div>
<div className='flex items-center gap-x-0.5 px-3 py-2'>
<RiCloseCircleLine className='w-4 h-4 text-components-button-ghost-text' />
<button type='button' className='px-0.5 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onBatchDisable}>
{t(`${i18nPrefix}.disable`)}
</button>
</div>
{onArchive && (
<div className='flex items-center gap-x-0.5 px-3 py-2'>
<RiArchive2Line className='w-4 h-4 text-components-button-ghost-text' />
<button type='button' className='px-0.5 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onArchive}>
{t(`${i18nPrefix}.archive`)}
</button>
</div>
)}
<div className='flex items-center gap-x-0.5 px-3 py-2'>
<RiDeleteBinLine className='w-4 h-4 text-components-button-destructive-ghost-text' />
<button type='button' className='px-0.5 text-components-button-destructive-ghost-text text-[13px] font-medium leading-[16px]' onClick={showDeleteConfirm}>
{t(`${i18nPrefix}.delete`)}
</button>
</div>
<Divider type='vertical' className='mx-0.5 h-3.5 bg-divider-regular' />
<button type='button' className='px-3.5 py-2 text-components-button-ghost-text text-[13px] font-medium leading-[16px]' onClick={onCancel}>
{t(`${i18nPrefix}.cancel`)}
</button>
</div>
{
isShowDeleteConfirm && (
<Confirm
isShow
title={t('datasetDocuments.list.delete.title')}
content={t('datasetDocuments.list.delete.content')}
confirmText={t('common.operation.sure')}
onConfirm={handleBatchDelete}
onCancel={hideDeleteConfirm}
isLoading={isDeleting}
isDisabled={isDeleting}
/>
)
}
</div>
)
}
export default React.memo(BatchAction)

View File

@@ -1,192 +0,0 @@
import React, { useEffect, useRef, useState } from 'react'
import type { ComponentProps, FC } from 'react'
import { useTranslation } from 'react-i18next'
import { ChunkingMode } from '@/models/datasets'
import classNames from '@/utils/classnames'
type IContentProps = ComponentProps<'textarea'>
const Textarea: FC<IContentProps> = React.memo(({
value,
placeholder,
className,
disabled,
...rest
}) => {
return (
<textarea
className={classNames(
'disabled:bg-transparent inset-0 outline-none border-none appearance-none resize-none w-full overflow-y-auto',
className,
)}
placeholder={placeholder}
value={value}
disabled={disabled}
{...rest}
/>
)
})
Textarea.displayName = 'Textarea'
type IAutoResizeTextAreaProps = ComponentProps<'textarea'> & {
containerRef: React.RefObject<HTMLDivElement>
labelRef: React.RefObject<HTMLDivElement>
}
const AutoResizeTextArea: FC<IAutoResizeTextAreaProps> = React.memo(({
className,
placeholder,
value,
disabled,
containerRef,
labelRef,
...rest
}) => {
const textareaRef = useRef<HTMLTextAreaElement>(null)
const observerRef = useRef<ResizeObserver>()
const [maxHeight, setMaxHeight] = useState(0)
useEffect(() => {
const textarea = textareaRef.current
if (!textarea)
return
textarea.style.height = 'auto'
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight)
const textareaHeight = Math.max(textarea.scrollHeight, lineHeight)
textarea.style.height = `${textareaHeight}px`
}, [value])
useEffect(() => {
const container = containerRef.current
const label = labelRef.current
if (!container || !label)
return
const updateMaxHeight = () => {
const containerHeight = container.clientHeight
const labelHeight = label.clientHeight
const padding = 32
const space = 12
const maxHeight = Math.floor((containerHeight - 2 * labelHeight - padding - space) / 2)
setMaxHeight(maxHeight)
}
updateMaxHeight()
observerRef.current = new ResizeObserver(updateMaxHeight)
observerRef.current.observe(container)
return () => {
observerRef.current?.disconnect()
}
}, [])
return (
<textarea
ref={textareaRef}
className={classNames(
'disabled:bg-transparent inset-0 outline-none border-none appearance-none resize-none w-full',
className,
)}
style={{
maxHeight,
}}
placeholder={placeholder}
value={value}
disabled={disabled}
{...rest}
/>
)
})
AutoResizeTextArea.displayName = 'AutoResizeTextArea'
type IQATextAreaProps = {
question: string
answer?: string
onQuestionChange: (question: string) => void
onAnswerChange?: (answer: string) => void
isEditMode?: boolean
}
const QATextArea: FC<IQATextAreaProps> = React.memo(({
question,
answer,
onQuestionChange,
onAnswerChange,
isEditMode = true,
}) => {
const { t } = useTranslation()
const containerRef = useRef<HTMLDivElement>(null)
const labelRef = useRef<HTMLDivElement>(null)
return (
<div ref={containerRef} className='h-full overflow-hidden'>
<div ref={labelRef} className='text-text-tertiary text-xs font-medium mb-1'>QUESTION</div>
<AutoResizeTextArea
className='text-text-secondary text-sm tracking-[-0.07px] caret-[#295EFF]'
value={question}
placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''}
onChange={e => onQuestionChange(e.target.value)}
disabled={!isEditMode}
containerRef={containerRef}
labelRef={labelRef}
/>
<div className='text-text-tertiary text-xs font-medium mb-1 mt-6'>ANSWER</div>
<AutoResizeTextArea
className='text-text-secondary text-sm tracking-[-0.07px] caret-[#295EFF]'
value={answer}
placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''}
onChange={e => onAnswerChange?.(e.target.value)}
disabled={!isEditMode}
autoFocus
containerRef={containerRef}
labelRef={labelRef}
/>
</div>
)
})
QATextArea.displayName = 'QATextArea'
type IChunkContentProps = {
question: string
answer?: string
onQuestionChange: (question: string) => void
onAnswerChange?: (answer: string) => void
isEditMode?: boolean
docForm: ChunkingMode
}
const ChunkContent: FC<IChunkContentProps> = ({
question,
answer,
onQuestionChange,
onAnswerChange,
isEditMode,
docForm,
}) => {
const { t } = useTranslation()
if (docForm === ChunkingMode.qa) {
return <QATextArea
question={question}
answer={answer}
onQuestionChange={onQuestionChange}
onAnswerChange={onAnswerChange}
isEditMode={isEditMode}
/>
}
return (
<Textarea
className='h-full w-full pb-6 body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
value={question}
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
onChange={e => onQuestionChange(e.target.value)}
disabled={!isEditMode}
autoFocus
/>
)
}
ChunkContent.displayName = 'ChunkContent'
export default React.memo(ChunkContent)

View File

@@ -1,11 +0,0 @@
import React from 'react'
const Dot = () => {
return (
<div className='text-text-quaternary system-xs-medium'>·</div>
)
}
Dot.displayName = 'Dot'
export default React.memo(Dot)

View File

@@ -1,78 +0,0 @@
import React, { type FC } from 'react'
import { RiFileList2Line } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
type IEmptyProps = {
onClearFilter: () => void
}
const EmptyCard = React.memo(() => {
return (
<div className='w-full h-32 rounded-xl opacity-30 bg-background-section-burn shrink-0' />
)
})
EmptyCard.displayName = 'EmptyCard'
type LineProps = {
className?: string
}
const Line = React.memo(({
className,
}: LineProps) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="2" height="241" viewBox="0 0 2 241" fill="none" className={className}>
<path d="M1 0.5L1 240.5" stroke="url(#paint0_linear_1989_74474)"/>
<defs>
<linearGradient id="paint0_linear_1989_74474" x1="-7.99584" y1="240.5" x2="-7.88094" y2="0.50004" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.01"/>
<stop offset="0.503965" stopColor="#101828" stopOpacity="0.08"/>
<stop offset="1" stopColor="white" stopOpacity="0.01"/>
</linearGradient>
</defs>
</svg>
)
})
Line.displayName = 'Line'
const Empty: FC<IEmptyProps> = ({
onClearFilter,
}) => {
const { t } = useTranslation()
return (
<div className={'h-full relative flex items-center justify-center z-0'}>
<div className='flex flex-col items-center'>
<div className='relative z-10 flex items-center justify-center w-14 h-14 border border-divider-subtle bg-components-card-bg rounded-xl shadow-lg shadow-shadow-shadow-5'>
<RiFileList2Line className='w-6 h-6 text-text-secondary' />
<Line className='absolute -right-[1px] top-1/2 -translate-y-1/2' />
<Line className='absolute -left-[1px] top-1/2 -translate-y-1/2' />
<Line className='absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
<Line className='absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
</div>
<div className='text-text-tertiary system-md-regular mt-3'>
{t('datasetDocuments.segment.empty')}
</div>
<button
type='button'
className='text-text-accent system-sm-medium mt-1'
onClick={onClearFilter}
>
{t('datasetDocuments.segment.clearFilter')}
</button>
</div>
<div className='h-full w-full absolute top-0 left-0 flex flex-col gap-y-3 -z-20 overflow-hidden'>
{
Array.from({ length: 10 }).map((_, i) => (
<EmptyCard key={i} />
))
}
</div>
<div className='h-full w-full absolute top-0 left-0 bg-dataset-chunk-list-mask-bg -z-10' />
</div>
)
}
export default React.memo(Empty)

View File

@@ -1,35 +0,0 @@
import React, { type FC } from 'react'
import Drawer from '@/app/components/base/drawer'
import classNames from '@/utils/classnames'
type IFullScreenDrawerProps = {
isOpen: boolean
onClose?: () => void
fullScreen: boolean
children: React.ReactNode
}
const FullScreenDrawer: FC<IFullScreenDrawerProps> = ({
isOpen,
onClose = () => {},
fullScreen,
children,
}) => {
return (
<Drawer
isOpen={isOpen}
onClose={onClose}
panelClassname={classNames('!p-0 bg-components-panel-bg',
fullScreen
? '!max-w-full !w-full'
: 'mt-16 mr-2 mb-2 !max-w-[560px] !w-[560px] border-[0.5px] border-components-panel-border rounded-xl',
)}
mask={false}
unmount
footer={null}
>
{children}
</Drawer>)
}
export default FullScreenDrawer

View File

@@ -1,47 +0,0 @@
import React, { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import classNames from '@/utils/classnames'
import type { SegmentDetailModel } from '@/models/datasets'
import TagInput from '@/app/components/base/tag-input'
type IKeywordsProps = {
segInfo?: Partial<SegmentDetailModel> & { id: string }
className?: string
keywords: string[]
onKeywordsChange: (keywords: string[]) => void
isEditMode?: boolean
actionType?: 'edit' | 'add' | 'view'
}
const Keywords: FC<IKeywordsProps> = ({
segInfo,
className,
keywords,
onKeywordsChange,
isEditMode,
actionType = 'view',
}) => {
const { t } = useTranslation()
return (
<div className={classNames('flex flex-col', className)}>
<div className='text-text-tertiary system-xs-medium-uppercase'>{t('datasetDocuments.segment.keywords')}</div>
<div className='text-text-tertiary w-full max-h-[200px] overflow-auto flex flex-wrap gap-1'>
{(!segInfo?.keywords?.length && actionType === 'view')
? '-'
: (
<TagInput
items={keywords}
onChange={newKeywords => onKeywordsChange(newKeywords)}
disableAdd={!isEditMode}
disableRemove={!isEditMode || (keywords.length === 1)}
/>
)
}
</div>
</div>
)
}
Keywords.displayName = 'Keywords'
export default React.memo(Keywords)

View File

@@ -1,131 +0,0 @@
import React, { type FC, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import { useCountDown } from 'ahooks'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import { useEventEmitterContextContext } from '@/context/event-emitter'
type IDefaultContentProps = {
onCancel: () => void
onConfirm: () => void
}
const DefaultContent: FC<IDefaultContentProps> = React.memo(({
onCancel,
onConfirm,
}) => {
const { t } = useTranslation()
return (
<>
<div className='pb-4'>
<span className='text-text-primary title-2xl-semi-bold'>{t('datasetDocuments.segment.regenerationConfirmTitle')}</span>
<p className='text-text-secondary system-md-regular'>{t('datasetDocuments.segment.regenerationConfirmMessage')}</p>
</div>
<div className='flex justify-end gap-x-2 pt-6'>
<Button onClick={onCancel}>
{t('common.operation.cancel')}
</Button>
<Button variant='warning' destructive onClick={onConfirm}>
{t('common.operation.regenerate')}
</Button>
</div>
</>
)
})
DefaultContent.displayName = 'DefaultContent'
const RegeneratingContent: FC = React.memo(() => {
const { t } = useTranslation()
return (
<>
<div className='pb-4'>
<span className='text-text-primary title-2xl-semi-bold'>{t('datasetDocuments.segment.regeneratingTitle')}</span>
<p className='text-text-secondary system-md-regular'>{t('datasetDocuments.segment.regeneratingMessage')}</p>
</div>
<div className='flex justify-end pt-6'>
<Button variant='warning' destructive disabled className='inline-flex items-center gap-x-0.5'>
<RiLoader2Line className='w-4 h-4 text-components-button-destructive-primary-text-disabled animate-spin' />
<span>{t('common.operation.regenerate')}</span>
</Button>
</div>
</>
)
})
RegeneratingContent.displayName = 'RegeneratingContent'
type IRegenerationCompletedContentProps = {
onClose: () => void
}
const RegenerationCompletedContent: FC<IRegenerationCompletedContentProps> = React.memo(({
onClose,
}) => {
const { t } = useTranslation()
const targetTime = useRef(Date.now() + 5000)
const [countdown] = useCountDown({
targetDate: targetTime.current,
onEnd: () => {
onClose()
},
})
return (
<>
<div className='pb-4'>
<span className='text-text-primary title-2xl-semi-bold'>{t('datasetDocuments.segment.regenerationSuccessTitle')}</span>
<p className='text-text-secondary system-md-regular'>{t('datasetDocuments.segment.regenerationSuccessMessage')}</p>
</div>
<div className='flex justify-end pt-6'>
<Button variant='primary' onClick={onClose}>
{`${t('common.operation.close')}${countdown === 0 ? '' : `(${Math.round(countdown / 1000)})`}`}
</Button>
</div>
</>
)
})
RegenerationCompletedContent.displayName = 'RegenerationCompletedContent'
type IRegenerationModalProps = {
isShow: boolean
onConfirm: () => void
onCancel: () => void
onClose: () => void
}
const RegenerationModal: FC<IRegenerationModalProps> = ({
isShow,
onConfirm,
onCancel,
onClose,
}) => {
const [loading, setLoading] = useState(false)
const [updateSucceeded, setUpdateSucceeded] = useState(false)
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v) => {
if (v === 'update-segment') {
setLoading(true)
setUpdateSucceeded(false)
}
if (v === 'update-segment-success')
setUpdateSucceeded(true)
if (v === 'update-segment-done')
setLoading(false)
})
return (
<Modal isShow={isShow} onClose={() => {}} className='!max-w-[480px] !rounded-2xl'>
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
{loading && !updateSucceeded && <RegeneratingContent />}
{!loading && updateSucceeded && <RegenerationCompletedContent onClose={onClose} />}
</Modal>
)
}
export default RegenerationModal

View File

@@ -1,40 +0,0 @@
import React, { type FC, useMemo } from 'react'
import { Chunk } from '@/app/components/base/icons/src/public/knowledge'
import cn from '@/utils/classnames'
type ISegmentIndexTagProps = {
positionId?: string | number
label?: string
className?: string
labelPrefix?: string
iconClassName?: string
labelClassName?: string
}
export const SegmentIndexTag: FC<ISegmentIndexTagProps> = ({
positionId,
label,
className,
labelPrefix = 'Chunk',
iconClassName,
labelClassName,
}) => {
const localPositionId = useMemo(() => {
const positionIdStr = String(positionId)
if (positionIdStr.length >= 2)
return `${labelPrefix}-${positionId}`
return `${labelPrefix}-${positionIdStr.padStart(2, '0')}`
}, [positionId, labelPrefix])
return (
<div className={cn('flex items-center', className)}>
<Chunk className={cn('w-3 h-3 p-[1px] text-text-tertiary mr-0.5', iconClassName)} />
<div className={cn('text-text-tertiary system-xs-medium', labelClassName)}>
{label || localPositionId}
</div>
</div>
)
}
SegmentIndexTag.displayName = 'SegmentIndexTag'
export default React.memo(SegmentIndexTag)

View File

@@ -1,15 +0,0 @@
import React from 'react'
import cn from '@/utils/classnames'
const Tag = ({ text, className }: { text: string; className?: string }) => {
return (
<div className={cn('inline-flex items-center gap-x-0.5', className)}>
<span className='text-text-quaternary text-xs font-medium'>#</span>
<span className='text-text-tertiary text-xs max-w-12 line-clamp-1 shrink-0'>{text}</span>
</div>
)
}
Tag.displayName = 'Tag'
export default React.memo(Tag)