mirror of https://github.com/langgenius/dify.git
feat: add regeneration confirmation and success messages in translation files
This commit is contained in:
parent
033ce47d01
commit
8518c1ae8c
|
|
@ -7,9 +7,10 @@ import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/co
|
|||
|
||||
type IActionButtonsProps = {
|
||||
handleCancel: () => void
|
||||
handleSave: (needRegenerate: boolean) => void
|
||||
handleSave: () => void
|
||||
loading: boolean
|
||||
actionType?: 'edit' | 'add'
|
||||
handleRegeneration?: () => void
|
||||
}
|
||||
|
||||
const ActionButtons: FC<IActionButtonsProps> = ({
|
||||
|
|
@ -17,6 +18,7 @@ const ActionButtons: FC<IActionButtonsProps> = ({
|
|||
handleSave,
|
||||
loading,
|
||||
actionType = 'edit',
|
||||
handleRegeneration,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode])
|
||||
|
|
@ -30,7 +32,7 @@ const ActionButtons: FC<IActionButtonsProps> = ({
|
|||
if (loading)
|
||||
return
|
||||
e.preventDefault()
|
||||
handleSave(false)
|
||||
handleSave()
|
||||
}
|
||||
, { exactMatch: true, useCapture: true })
|
||||
|
||||
|
|
@ -50,18 +52,18 @@ const ActionButtons: FC<IActionButtonsProps> = ({
|
|||
</Button>
|
||||
{(isParentChildParagraphMode && actionType === 'edit')
|
||||
? <Button
|
||||
onClick={handleSave.bind(null, true)}
|
||||
onClick={handleRegeneration}
|
||||
disabled={loading}
|
||||
>
|
||||
<span className='text-components-button-secondary-text system-sm-medium'>
|
||||
{t('datasetDocuments.segment.saveAndRegenerate')}
|
||||
{t('common.operation.saveAndRegenerate')}
|
||||
</span>
|
||||
</Button>
|
||||
: null
|
||||
}
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={handleSave.bind(null, false)}
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
<div className='flex items-center gap-x-1'>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
import React, { type FC, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
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='p-6 pb-4'>
|
||||
<span className='text-text-primary title-2xl-semi-bold'>{t('datasetDocuments.segment.regenerationConfirm')}</span>
|
||||
<p className='text-text-secondary system-md-regular'>{t('datasetDocuments.segment.regenerationWarning')}</p>
|
||||
</div>
|
||||
<div className='flex justify-end gap-x-2 p-6'>
|
||||
<Button onClick={onCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button destructive onClick={onConfirm}>
|
||||
{t('common.operation.regenerate')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
const RegeneratingContent: FC = React.memo(() => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='p-6 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 p-6'>
|
||||
<Button destructive disabled className='inline-flex items-center gap-x-0.5'>
|
||||
<RiLoader2Line className='w-4 h-4 text-components-button-destructive-primary-text-disabled' />
|
||||
<span>{t('common.operation.regenerate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
type IRegenerationCompletedContentProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const RegenerationCompletedContent: FC<IRegenerationCompletedContentProps> = React.memo(({
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [countDown, setCountDown] = useState(5)
|
||||
const timerRef = useRef<any>(null)
|
||||
|
||||
useEffect(() => {
|
||||
timerRef.current = setInterval(() => {
|
||||
if (countDown > 0)
|
||||
setCountDown(countDown - 1)
|
||||
else
|
||||
clearInterval(timerRef.current)
|
||||
}, 1000)
|
||||
return () => {
|
||||
clearInterval(timerRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='p-6 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 p-6'>
|
||||
<Button variant='primary' onClick={onClose}>
|
||||
{`${t('common.operation.close')}(${countDown})`}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
type IRegenerationModalProps = {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const RegenerationModal: FC<IRegenerationModalProps> = ({
|
||||
isShow,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [updateSuccess, setUpdateSuccess] = useState(false)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
eventEmitter?.useSubscription((v) => {
|
||||
if (v === 'update-segment') {
|
||||
setLoading(true)
|
||||
setUpdateSuccess(false)
|
||||
}
|
||||
if (v === 'update-segment-success')
|
||||
setUpdateSuccess(true)
|
||||
if (v === 'update-segment-done')
|
||||
setLoading(false)
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={() => {}} className='!max-w-[480px] !rounded-2xl'>
|
||||
{(!loading && !updateSuccess) && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
|
||||
{(loading && !updateSuccess) && <RegeneratingContent />}
|
||||
{!loading && updateSuccess && <RegenerationCompletedContent onClose={onCancel} />}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegenerationModal
|
||||
|
|
@ -76,7 +76,6 @@ type ICompletedProps = {
|
|||
onNewSegmentModalChange: (state: boolean) => void
|
||||
importStatus: ProcessStatus | string | undefined
|
||||
archived?: boolean
|
||||
// data: Array<{}> // all/part segments
|
||||
}
|
||||
/**
|
||||
* Embedding done, show list of all segments
|
||||
|
|
@ -247,7 +246,7 @@ const Completed: FC<ICompletedProps> = ({
|
|||
question: string,
|
||||
answer: string,
|
||||
keywords: string[],
|
||||
needRegenerate: boolean,
|
||||
needRegenerate = false,
|
||||
) => {
|
||||
const params: SegmentUpdater = { content: '' }
|
||||
if (docForm === 'qa_model') {
|
||||
|
|
@ -290,9 +289,10 @@ const Completed: FC<ICompletedProps> = ({
|
|||
}
|
||||
}
|
||||
setSegments([...segments])
|
||||
eventEmitter?.emit('update-segment-success')
|
||||
}
|
||||
finally {
|
||||
eventEmitter?.emit('')
|
||||
eventEmitter?.emit('update-segment-done')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { type FC, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightUpLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { StatusItem } from '../../list'
|
||||
import DocumentFileIcon from '../../../common/document-file-icon'
|
||||
import { useDocumentContext } from '../index'
|
||||
import ChildSegmentList from './child-segment-list'
|
||||
import Tag from './common/tag'
|
||||
import Dot from './common/dot'
|
||||
import { SegmentIndexTag, useSegmentListContext } from '.'
|
||||
import type { SegmentDetailModel } from '@/models/datasets'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
|
|
@ -17,74 +18,13 @@ import Badge from '@/app/components/base/badge'
|
|||
import { isAfter } from '@/utils/time'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
const Dot = React.memo(() => {
|
||||
return (
|
||||
<div className='text-text-quaternary text-xs font-medium'>·</div>
|
||||
)
|
||||
})
|
||||
|
||||
Dot.displayName = 'Dot'
|
||||
|
||||
const ProgressBar: FC<{ percent: number; loading: boolean }> = React.memo(({ percent, loading }) => {
|
||||
return (
|
||||
<div className=''>
|
||||
<div className=''>
|
||||
<div
|
||||
className=''
|
||||
style={{ width: `${loading ? 0 : (Math.min(percent, 1) * 100).toFixed(2)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className=''>{loading ? null : percent.toFixed(2)}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
ProgressBar.displayName = 'ProgressBar'
|
||||
|
||||
type DocumentTitleProps = {
|
||||
name: string
|
||||
extension?: string
|
||||
}
|
||||
|
||||
const DocumentTitle: FC<DocumentTitleProps> = React.memo(({ extension, name }) => {
|
||||
return (
|
||||
<div className=''>
|
||||
<DocumentFileIcon name={name} extension={extension} size={'sm'} />
|
||||
<span className=''>{name || '--'}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
DocumentTitle.displayName = 'DocumentTitle'
|
||||
|
||||
const Tag = React.memo(({ text }: { text: string }) => {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-x-0.5'>
|
||||
<span className='text-text-quaternary text-xs font-medium'>#</span>
|
||||
<span className='text-text-tertiary text-xs'>{text}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
Tag.displayName = 'Tag'
|
||||
|
||||
export type UsageScene = 'doc' | 'hitTesting'
|
||||
|
||||
type ISegmentCardProps = {
|
||||
loading: boolean
|
||||
detail?: SegmentDetailModel & { document?: { name: string } }
|
||||
contentExternal?: string
|
||||
refSource?: {
|
||||
title: string
|
||||
uri: string
|
||||
}
|
||||
isExternal?: boolean
|
||||
score?: number
|
||||
onClick?: () => void
|
||||
onChangeSwitch?: (enabled: boolean, segId?: string) => Promise<void>
|
||||
onDelete?: (segId: string) => Promise<void>
|
||||
onClickEdit?: () => void
|
||||
scene?: UsageScene
|
||||
className?: string
|
||||
archived?: boolean
|
||||
embeddingAvailable?: boolean
|
||||
|
|
@ -92,16 +32,11 @@ type ISegmentCardProps = {
|
|||
|
||||
const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
detail = {},
|
||||
contentExternal,
|
||||
isExternal,
|
||||
refSource,
|
||||
score,
|
||||
onClick,
|
||||
onChangeSwitch,
|
||||
onDelete,
|
||||
onClickEdit,
|
||||
loading = true,
|
||||
scene = 'doc',
|
||||
className = '',
|
||||
archived,
|
||||
embeddingAvailable,
|
||||
|
|
@ -124,10 +59,6 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||
const isCollapsed = useSegmentListContext(s => s.isCollapsed)
|
||||
const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode])
|
||||
|
||||
const isDocScene = useMemo(() => {
|
||||
return scene === 'doc'
|
||||
}, [scene])
|
||||
|
||||
const isGeneralMode = useMemo(() => {
|
||||
return mode === 'custom'
|
||||
}, [mode])
|
||||
|
|
@ -147,9 +78,9 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||
}, [enabled])
|
||||
|
||||
const handleClickCard = useCallback(() => {
|
||||
if (!isFullDocMode)
|
||||
if (mode !== 'hierarchical' || parentMode !== 'full-doc')
|
||||
onClick?.()
|
||||
}, [isFullDocMode, onClick])
|
||||
}, [mode, parentMode, onClick])
|
||||
|
||||
const renderContent = () => {
|
||||
if (answer) {
|
||||
|
|
@ -166,10 +97,6 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (contentExternal)
|
||||
return contentExternal
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
|
|
@ -179,96 +106,85 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||
onClick={handleClickCard}
|
||||
>
|
||||
<div className='h-5 relative flex items-center justify-between'>
|
||||
{isDocScene
|
||||
? <>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<SegmentIndexTag positionId={position} className={textOpacity} />
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div>
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(hit_count)} Retrieval Count`}</div>
|
||||
{chunkEdited && (
|
||||
<>
|
||||
<Dot />
|
||||
<Badge text='edited' uppercase className={textOpacity} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!isFullDocMode
|
||||
? <div className='flex items-center'>
|
||||
{loading
|
||||
? (
|
||||
<Indicator color="gray" />
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
||||
{embeddingAvailable && (
|
||||
<div className="absolute -top-2 -right-2.5 z-20 hidden group-hover/card:flex items-center gap-x-0.5 p-1
|
||||
rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]">
|
||||
{!archived && (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent='Edit'
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<div
|
||||
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onClickEdit?.()
|
||||
}}>
|
||||
<RiEditLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent='Delete'
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowModal(true)
|
||||
}
|
||||
}>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Divider type="vertical" className="h-3.5 bg-divider-regular" />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
className="flex items-center"
|
||||
>
|
||||
<Switch
|
||||
size='md'
|
||||
disabled={archived || detail.status !== 'completed'}
|
||||
defaultValue={enabled}
|
||||
onChange={async (val) => {
|
||||
await onChangeSwitch?.(val, id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<SegmentIndexTag positionId={position} className={textOpacity} />
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div>
|
||||
<Dot />
|
||||
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(hit_count)} Retrieval Count`}</div>
|
||||
{chunkEdited && (
|
||||
<>
|
||||
<Dot />
|
||||
<Badge text='edited' uppercase className={textOpacity} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!isFullDocMode
|
||||
? <div className='flex items-center'>
|
||||
{loading
|
||||
? (
|
||||
<Indicator color="gray" />
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
||||
{embeddingAvailable && (
|
||||
<div className="absolute -top-2 -right-2.5 z-20 hidden group-hover/card:flex items-center gap-x-0.5 p-1
|
||||
rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-md backdrop-blur-[5px]">
|
||||
{!archived && (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent='Edit'
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<div
|
||||
className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onClickEdit?.()
|
||||
}}>
|
||||
<RiEditLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent='Delete'
|
||||
popupClassName='text-text-secondary system-xs-medium'
|
||||
>
|
||||
<div className='shrink-0 w-6 h-6 flex items-center justify-center rounded-lg hover:bg-state-destructive-hover cursor-pointer group/delete'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setShowModal(true)
|
||||
}
|
||||
}>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary group-hover/delete:text-text-destructive' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Divider type="vertical" className="h-3.5 bg-divider-regular" />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
className="flex items-center"
|
||||
>
|
||||
<Switch
|
||||
size='md'
|
||||
disabled={archived || detail.status !== 'completed'}
|
||||
defaultValue={enabled}
|
||||
onChange={async (val) => {
|
||||
await onChangeSwitch?.(val, id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
: null}
|
||||
</>
|
||||
: (
|
||||
score !== null
|
||||
? (
|
||||
<div className=''>
|
||||
<div className='' />
|
||||
<ProgressBar percent={score ?? 0} loading={loading} />
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
: null}
|
||||
</>
|
||||
</div>
|
||||
{loading
|
||||
? (
|
||||
|
|
@ -277,50 +193,32 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||
</div>
|
||||
)
|
||||
: (
|
||||
isDocScene
|
||||
? <>
|
||||
<div className={cn('text-text-secondary body-md-regular -tracking-[0.07px] mt-0.5',
|
||||
textOpacity,
|
||||
isCollapsed ? 'line-clamp-2' : 'line-clamp-20',
|
||||
)}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
{isGeneralMode && <div className={cn('flex items-center gap-x-2 py-1.5', textOpacity)}>
|
||||
{keywords?.map(keyword => <Tag key={keyword} text={keyword} />)}
|
||||
</div>}
|
||||
{
|
||||
isFullDocMode
|
||||
? <button className='mt-0.5 mb-2 text-text-accent system-xs-semibold-uppercase' onClick={() => onClick?.()}>VIEW MORE</button>
|
||||
: null
|
||||
}
|
||||
{
|
||||
child_chunks.length > 0
|
||||
&& <ChildSegmentList
|
||||
childChunks={child_chunks}
|
||||
handleInputChange={() => {}}
|
||||
enabled={enabled}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
: <>
|
||||
<div className='text-text-secondary body-md-regular -tracking-[0.07px]'>
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className=''>
|
||||
<Divider />
|
||||
<div className="relative flex items-center w-full pb-1">
|
||||
<DocumentTitle
|
||||
name={detail?.document?.name || refSource?.title || ''}
|
||||
extension={(detail?.document?.name || refSource?.title || '').split('.').pop() || 'txt'}
|
||||
/>
|
||||
<div className=''>
|
||||
{isExternal ? t('datasetHitTesting.viewDetail') : t('datasetHitTesting.viewChart')}
|
||||
<RiArrowRightUpLine className="w-3.5 h-3.5 ml-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<div className={cn('text-text-secondary body-md-regular -tracking-[0.07px] mt-0.5',
|
||||
textOpacity,
|
||||
isCollapsed ? 'line-clamp-2' : 'line-clamp-20',
|
||||
)}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
{isGeneralMode && <div className={cn('flex items-center gap-x-2 py-1.5', textOpacity)}>
|
||||
{keywords?.map(keyword => <Tag key={keyword} text={keyword} />)}
|
||||
</div>}
|
||||
{
|
||||
isFullDocMode
|
||||
? <button className='mt-0.5 mb-2 text-text-accent system-xs-semibold-uppercase' onClick={() => onClick?.()}>VIEW MORE</button>
|
||||
: null
|
||||
}
|
||||
{
|
||||
child_chunks.length > 0
|
||||
&& <ChildSegmentList
|
||||
childChunks={child_chunks}
|
||||
handleInputChange={() => {}}
|
||||
enabled={enabled}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{showModal
|
||||
&& <Confirm
|
||||
isShow={showModal}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useDocumentContext } from '../index'
|
|||
import ActionButtons from './common/action-buttons'
|
||||
import ChunkContent from './common/chunk-content'
|
||||
import Keywords from './common/keywords'
|
||||
import RegenerationModal from './common/regeneration-modal'
|
||||
import { SegmentIndexTag, useSegmentListContext } from './index'
|
||||
import type { SegmentDetailModel } from '@/models/datasets'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
|
@ -17,7 +18,7 @@ import Divider from '@/app/components/base/divider'
|
|||
|
||||
type ISegmentDetailProps = {
|
||||
segInfo?: Partial<SegmentDetailModel> & { id: string }
|
||||
onUpdate: (segmentId: string, q: string, a: string, k: string[], needRegenerate: boolean) => void
|
||||
onUpdate: (segmentId: string, q: string, a: string, k: string[], needRegenerate?: boolean) => void
|
||||
onCancel: () => void
|
||||
isEditMode?: boolean
|
||||
docForm: string
|
||||
|
|
@ -39,13 +40,14 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
|||
const [keywords, setKeywords] = useState<string[]>(segInfo?.keywords || [])
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showRegenerationModal, setShowRegenerationModal] = useState(false)
|
||||
const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen])
|
||||
const [mode] = useDocumentContext(s => s.mode)
|
||||
const mode = useDocumentContext(s => s.mode)
|
||||
|
||||
eventEmitter?.useSubscription((v) => {
|
||||
if (v === 'update-segment')
|
||||
setLoading(true)
|
||||
else
|
||||
if (v === 'update-segment-done')
|
||||
setLoading(false)
|
||||
})
|
||||
|
||||
|
|
@ -56,8 +58,20 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
|||
setKeywords(segInfo?.keywords || [])
|
||||
}
|
||||
|
||||
const handleSave = (needRegenerate = false) => {
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, needRegenerate)
|
||||
const handleSave = () => {
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords)
|
||||
}
|
||||
|
||||
const handleRegeneration = () => {
|
||||
setShowRegenerationModal(true)
|
||||
}
|
||||
|
||||
const onCancelRegeneration = () => {
|
||||
setShowRegenerationModal(false)
|
||||
}
|
||||
|
||||
const onConfirmRegeneration = () => {
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, true)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -74,7 +88,12 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
|||
<div className='flex items-center'>
|
||||
{isEditMode && fullScreen && (
|
||||
<>
|
||||
<ActionButtons handleCancel={handleCancel} handleSave={handleSave} loading={loading} />
|
||||
<ActionButtons
|
||||
handleCancel={handleCancel}
|
||||
handleRegeneration={handleRegeneration}
|
||||
handleSave={handleSave}
|
||||
loading={loading}
|
||||
/>
|
||||
<Divider type='vertical' className='h-3.5 bg-divider-regular ml-4 mr-2' />
|
||||
</>
|
||||
)}
|
||||
|
|
@ -108,9 +127,19 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
|||
</div>
|
||||
{isEditMode && !fullScreen && (
|
||||
<div className='flex items-center justify-end p-4 pt-3 border-t-[1px] border-t-divider-subtle'>
|
||||
<ActionButtons handleCancel={handleCancel} handleSave={handleSave} loading={loading} />
|
||||
<ActionButtons
|
||||
handleCancel={handleCancel}
|
||||
handleRegeneration={handleRegeneration}
|
||||
handleSave={handleSave}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<RegenerationModal
|
||||
isShow={showRegenerationModal}
|
||||
onConfirm={onConfirmRegeneration}
|
||||
onCancel={onCancelRegeneration}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ const translation = {
|
|||
zoomOut: 'Zoom Out',
|
||||
zoomIn: 'Zoom In',
|
||||
openInNewTab: 'Open in new tab',
|
||||
saveAndRegenerate: 'Save & Regenerate Child Chunks',
|
||||
close: 'Close',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} is required',
|
||||
|
|
|
|||
|
|
@ -353,7 +353,12 @@ const translation = {
|
|||
delete: 'Delete this chunk ?',
|
||||
chunkAdded: '1 chunk added',
|
||||
viewAddedChunk: 'View',
|
||||
saveAndRegenerate: 'Save & Regenerate Child Chunks',
|
||||
regenerationConfirmTitle: 'Do you want to regenerate child chunks?',
|
||||
regenerationConfirmMessage: 'Regenerating child chunks will overwrite the current child chunks, including edited chunks and newly added chunks. The regeneration cannot be undone.',
|
||||
regeneratingTitle: 'Regenerating child chunks',
|
||||
regeneratingMessage: 'This may take a moment, please wait...',
|
||||
regenerationSuccessTitle: 'Regeneration completed',
|
||||
regenerationSuccessMessage: 'You can close this window.',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ const translation = {
|
|||
zoomOut: '缩小',
|
||||
zoomIn: '放大',
|
||||
openInNewTab: '在新标签页打开',
|
||||
saveAndRegenerate: '保存并重新生成子分段',
|
||||
close: '关闭',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} 为必填项',
|
||||
|
|
|
|||
|
|
@ -351,7 +351,12 @@ const translation = {
|
|||
delete: '删除这个分段?',
|
||||
chunkAdded: '新增一个分段',
|
||||
viewAddedChunk: '查看',
|
||||
saveAndRegenerate: '保存并重新生成子分段',
|
||||
regenerationConfirmTitle: '是否需要重新生成子分段?',
|
||||
regenerationConfirmMessage: '重新生成的子分段将会覆盖当前的子分段,包括编辑过的分段和新添加的分段。重新生成操作无法撤销。',
|
||||
regeneratingTitle: '正在生成子分段',
|
||||
regeneratingMessage: '生成子分段需要一些时间,请耐心等待...',
|
||||
regenerationSuccessTitle: '子分段已重新生成',
|
||||
regenerationSuccessMessage: '可以关闭窗口',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue