feat: update translation files and improve segment index tag component

This commit is contained in:
twwu 2024-12-11 18:15:30 +08:00
parent 4017c65c1f
commit 51f6a87aef
18 changed files with 147 additions and 85 deletions

View File

@ -8,7 +8,7 @@ import {
import { StatusItem } from '../../list'
import style from '../../style.module.css'
import s from './style.module.css'
import { SegmentIndexTag } from './index'
import { SegmentIndexTag } from './common/segment-index-tag'
import cn from '@/utils/classnames'
import Confirm from '@/app/components/base/confirm'
import Switch from '@/app/components/base/switch'

View File

@ -1,6 +1,7 @@
import React, { type FC, useEffect, useRef, useState } from 'react'
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'
@ -18,15 +19,15 @@ const DefaultContent: FC<IDefaultContentProps> = React.memo(({
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 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 p-6'>
<div className='flex justify-end gap-x-2 pt-6'>
<Button onClick={onCancel}>
{t('common.operation.cancel')}
</Button>
<Button destructive onClick={onConfirm}>
<Button variant='warning' destructive onClick={onConfirm}>
{t('common.operation.regenerate')}
</Button>
</div>
@ -34,18 +35,20 @@ const DefaultContent: FC<IDefaultContentProps> = React.memo(({
)
})
DefaultContent.displayName = 'DefaultContent'
const RegeneratingContent: FC = React.memo(() => {
const { t } = useTranslation()
return (
<>
<div className='p-6 pb-4'>
<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 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' />
<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>
@ -53,6 +56,8 @@ const RegeneratingContent: FC = React.memo(() => {
)
})
RegeneratingContent.displayName = 'RegeneratingContent'
type IRegenerationCompletedContentProps = {
onClose: () => void
}
@ -61,67 +66,64 @@ const RegenerationCompletedContent: FC<IRegenerationCompletedContentProps> = Rea
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)
}
}, [])
const targetTime = useRef(Date.now() + 5000)
const [countdown] = useCountDown({
targetDate: targetTime.current,
onEnd: () => {
onClose()
},
})
return (
<>
<div className='p-6 pb-4'>
<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 p-6'>
<div className='flex justify-end pt-6'>
<Button variant='primary' onClick={onClose}>
{`${t('common.operation.close')}(${countDown})`}
{`${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 [updateSuccess, setUpdateSuccess] = useState(false)
const [updateSucceeded, setUpdateSucceeded] = useState(false)
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v) => {
if (v === 'update-segment') {
setLoading(true)
setUpdateSuccess(false)
setUpdateSucceeded(false)
}
if (v === 'update-segment-success')
setUpdateSuccess(true)
setUpdateSucceeded(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} />}
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
{loading && !updateSucceeded && <RegeneratingContent />}
{!loading && updateSucceeded && <RegenerationCompletedContent onClose={onClose} />}
</Modal>
)
}

View File

@ -0,0 +1,36 @@
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
}
export const SegmentIndexTag: FC<ISegmentIndexTagProps> = ({
positionId,
label,
className,
labelPrefix = 'Chunk',
}) => {
const localPositionId = useMemo(() => {
const positionIdStr = String(positionId)
if (positionIdStr.length >= 3)
return `${labelPrefix}-${positionId}`
return `${labelPrefix}-${positionIdStr.padStart(2, '0')}`
}, [positionId, labelPrefix])
return (
<div className={cn('flex items-center', className)}>
<Chunk className='w-3 h-3 p-[1px] text-text-tertiary mr-0.5' />
<div className='text-text-tertiary system-xs-medium'>
{label || localPositionId}
</div>
</div>
)
}
SegmentIndexTag.displayName = 'SegmentIndexTag'
export default React.memo(SegmentIndexTag)

View File

@ -13,7 +13,7 @@ import BatchAction from './batch-action'
import SegmentDetail from './segment-detail'
import SegmentCard from './segment-card'
import ChildSegmentList from './child-segment-list'
import FullScreenDrawer from './full-screen-drawer'
import FullScreenDrawer from './common/full-screen-drawer'
import Pagination from '@/app/components/base/pagination'
import cn from '@/utils/classnames'
import { formatNumber } from '@/utils/format'
@ -192,7 +192,6 @@ const Completed: FC<ICompletedProps> = ({
}
const { mutateAsync: enableSegment } = useEnableSegment()
const { mutateAsync: disableSegment } = useDisableSegment()
const onChangeSwitch = useCallback(async (enable: boolean, segId?: string) => {
@ -229,18 +228,6 @@ const Completed: FC<ICompletedProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [datasetId, documentId, selectedSegmentIds])
const onCancelBatchOperation = useCallback(() => {
setSelectedSegmentIds([])
}, [])
const onSelected = useCallback((segId: string) => {
setSelectedSegmentIds(prev =>
prev.includes(segId)
? prev.filter(id => id !== segId)
: [...prev, segId],
)
}, [])
const handleUpdateSegment = async (
segmentId: string,
question: string,
@ -275,6 +262,7 @@ const Completed: FC<ICompletedProps> = ({
eventEmitter?.emit('update-segment')
const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
if (!needRegenerate)
onCloseDrawer()
for (const seg of segments) {
if (seg.id === segmentId) {
@ -301,6 +289,18 @@ const Completed: FC<ICompletedProps> = ({
resetList()
}, [importStatus, resetList])
const onCancelBatchOperation = useCallback(() => {
setSelectedSegmentIds([])
}, [])
const onSelected = useCallback((segId: string) => {
setSelectedSegmentIds(prev =>
prev.includes(segId)
? prev.filter(id => id !== segId)
: [...prev, segId],
)
}, [])
const isAllSelected = useMemo(() => {
return segments.length > 0 && segments.every(seg => selectedSegmentIds.includes(seg.id))
}, [segments, selectedSegmentIds])

View File

@ -6,7 +6,8 @@ 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 { SegmentIndexTag } from './common/segment-index-tag'
import { useSegmentListContext } from './index'
import type { SegmentDetailModel } from '@/models/datasets'
import Indicator from '@/app/components/header/indicator'
import Switch from '@/app/components/base/switch'
@ -108,7 +109,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
<div className='h-5 relative flex items-center justify-between'>
<>
<div className='flex items-center gap-x-2'>
<SegmentIndexTag positionId={position} className={textOpacity} />
<SegmentIndexTag positionId={position} className={textOpacity} labelPrefix={isGeneralMode ? 'Chunk' : 'Parent-Chunk'} />
<Dot />
<div className={cn('text-text-tertiary system-xs-medium', textOpacity)}>{`${formatNumber(word_count)} Characters`}</div>
<Dot />
@ -205,7 +206,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
</div>}
{
isFullDocMode
? <button className='mt-0.5 mb-2 text-text-accent system-xs-semibold-uppercase' onClick={() => onClick?.()}>VIEW MORE</button>
? <button className='mt-0.5 mb-2 text-text-accent system-xs-semibold-uppercase' onClick={() => onClick?.()}>{t('common.operation.viewMore')}</button>
: null
}
{

View File

@ -1,4 +1,4 @@
import React, { type FC, useState } from 'react'
import React, { type FC, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiCloseLine,
@ -9,7 +9,8 @@ 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 { SegmentIndexTag } from './common/segment-index-tag'
import { useSegmentListContext } from './index'
import type { SegmentDetailModel } from '@/models/datasets'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { formatNumber } from '@/utils/format'
@ -74,13 +75,17 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
onUpdate(segInfo?.id || '', question, answer, keywords, true)
}
const isParentChildMode = useMemo(() => {
return mode === 'hierarchical'
}, [mode])
return (
<div className={'flex flex-col h-full'}>
<div className={classNames('flex items-center justify-between', fullScreen ? 'py-3 pr-4 pl-6 border border-divider-subtle' : 'pt-3 pr-3 pl-4')}>
<div className='flex flex-col'>
<div className='text-text-primary system-xl-semibold'>{isEditMode ? 'Edit Chunk' : 'Chunk Detail'}</div>
<div className='flex items-center gap-x-2'>
<SegmentIndexTag positionId={segInfo?.position || ''} />
<SegmentIndexTag positionId={segInfo?.position || ''} labelPrefix={isParentChildMode ? 'Parent-Chunk' : 'Chunk'} />
<span className='text-text-quaternary system-xs-medium'>·</span>
<span className='text-text-tertiary system-xs-medium'>{formatNumber(isEditMode ? question.length : segInfo?.word_count as number)} {t('datasetDocuments.segment.characters')}</span>
</div>
@ -135,11 +140,16 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
/>
</div>
)}
{
showRegenerationModal && (
<RegenerationModal
isShow={showRegenerationModal}
onConfirm={onConfirmRegeneration}
onCancel={onCancelRegeneration}
onClose={onCancelRegeneration}
/>
)
}
</div>
)
}

View File

@ -5,7 +5,8 @@ import { useContext } from 'use-context-selector'
import { useParams } from 'next/navigation'
import { RiCloseLine, RiExpandDiagonalLine } from '@remixicon/react'
import { useShallow } from 'zustand/react/shallow'
import { SegmentIndexTag, useSegmentListContext } from './completed'
import { useSegmentListContext } from './completed'
import { SegmentIndexTag } from './completed/common/segment-index-tag'
import ActionButtons from './completed/common/action-buttons'
import Keywords from './completed/common/keywords'
import ChunkContent from './completed/common/chunk-content'
@ -41,7 +42,7 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
const [loading, setLoading] = useState(false)
const [addAnother, setAddAnother] = useState(true)
const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen])
const [mode] = useDocumentContext(s => s.mode)
const mode = useDocumentContext(s => s.mode)
const { appSidebarExpand } = useAppStore(useShallow(state => ({
appSidebarExpand: state.appSidebarExpand,
})))

View File

@ -70,7 +70,8 @@ export const EditSlice: FC<EditSliceProps> = (props) => {
onMouseLeave={() => setDelBtnHover(false)}
>
<ActionButton
onClick={() => {
onClick={(e) => {
e.stopPropagation()
onDelete()
setDelBtnShow(false)
}}

View File

@ -2,7 +2,7 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { SegmentIndexTag } from '../../documents/detail/completed'
import { SegmentIndexTag } from '../../documents/detail/completed/common/segment-index-tag'
import type { HitTesting } from '@/models/datasets'
import cn from '@/utils/classnames'
type Props = {

View File

@ -1,7 +1,7 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { SegmentIndexTag } from '../documents/detail/completed'
import { SegmentIndexTag } from '../documents/detail/completed/common/segment-index-tag'
import s from '../documents/detail/completed/style.module.css'
import cn from '@/utils/classnames'
import type { SegmentDetailModel } from '@/models/datasets'

View File

@ -44,6 +44,8 @@ const translation = {
openInNewTab: 'Open in new tab',
saveAndRegenerate: 'Save & Regenerate Child Chunks',
close: 'Close',
viewMore: 'VIEW MORE',
regenerate: 'Regenerate',
},
errorMsg: {
fieldRequired: '{{field}} is required',

View File

@ -349,6 +349,7 @@ const translation = {
newTextSegment: 'New Text Segment',
newQaSegment: 'New Q&A Segment',
addChunk: 'Add Chunk',
addChildChunk: 'Add Child Chunk',
addAnother: 'Add another',
delete: 'Delete this chunk ?',
chunkAdded: '1 chunk added',

View File

@ -44,6 +44,8 @@ const translation = {
openInNewTab: '在新标签页打开',
saveAndRegenerate: '保存并重新生成子分段',
close: '关闭',
viewMore: '查看更多',
regenerate: '重新生成',
},
errorMsg: {
fieldRequired: '{{field}} 为必填项',

View File

@ -347,6 +347,7 @@ const translation = {
newTextSegment: '新文本分段',
newQaSegment: '新问答分段',
addChunk: '新增分段',
addChildChunk: '新增子分段',
addAnother: '连续新增',
delete: '删除这个分段?',
chunkAdded: '新增一个分段',

View File

@ -624,7 +624,7 @@ export type ChildChunkDetail = {
type: ChildChunkType
}
export type ChildSegmentResponse = {
export type ChildSegmentsResponse = {
data: ChildChunkDetail[]
total: number
total_pages: number

View File

@ -25,8 +25,6 @@ import type {
RelatedAppResponse,
SegmentDetailModel,
SegmentUpdater,
SegmentsQuery,
SegmentsResponse,
createDocumentResponse,
} from '@/models/datasets'
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
@ -180,19 +178,6 @@ export const modifyDocMetadata: Fetcher<CommonResponse, CommonDocReq & { body: {
}
// apis for segments in a document
export const fetchSegments: Fetcher<SegmentsResponse, CommonDocReq & { params: SegmentsQuery }> = ({ datasetId, documentId, params }) => {
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
}
export const enableSegment: Fetcher<CommonResponse, { datasetId: string; segmentId: string }> = ({ datasetId, segmentId }) => {
return patch<CommonResponse>(`/datasets/${datasetId}/segments/${segmentId}/enable`)
}
export const disableSegment: Fetcher<CommonResponse, { datasetId: string; segmentId: string }> = ({ datasetId, segmentId }) => {
return patch<CommonResponse>(`/datasets/${datasetId}/segments/${segmentId}/disable`)
}
export const updateSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string }, { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }> = ({ datasetId, documentId, segmentId, body }) => {
return patch<{ data: SegmentDetailModel; doc_form: string }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body })
}

View File

@ -1,7 +1,7 @@
import { useMutation, useQuery } from '@tanstack/react-query'
import { del, get, patch } from '../base'
import { del, get, patch, post } from '../base'
import type { CommonResponse } from '@/models/common'
import type { ChildSegmentResponse, SegmentsResponse } from '@/models/datasets'
import type { ChildChunkDetail, ChildSegmentsResponse, SegmentsResponse } from '@/models/datasets'
const NAME_SPACE = 'segment'
@ -65,7 +65,7 @@ export const useDeleteSegment = () => {
})
}
const useChildSegmentListKey = [NAME_SPACE, 'childChunkList']
export const useChildSegmentListKey = [NAME_SPACE, 'childChunkList']
export const useChildSegmentList = (
payload: {
@ -85,9 +85,29 @@ export const useChildSegmentList = (
return useQuery({
queryKey: [...useChildSegmentListKey, datasetId, documentId, segmentId, page, limit, keyword],
queryFn: () => {
return get<ChildSegmentResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { params })
return get<ChildSegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { params })
},
enabled: !disable,
initialData: disable ? { data: [], total: 0, page: 1, total_pages: 0, limit: 10 } : undefined,
})
}
export const useDeleteChildSegment = () => {
return useMutation({
mutationKey: [NAME_SPACE, 'childChunk', 'delete'],
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string }) => {
const { datasetId, documentId, segmentId, childChunkId } = payload
return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks/${childChunkId}`)
},
})
}
export const useAddChildSegment = () => {
return useMutation({
mutationKey: [NAME_SPACE, 'childChunk', 'add'],
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: { content: string } }) => {
const { datasetId, documentId, segmentId, body } = payload
return post<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segment/${segmentId}/child_chunks`, { body })
},
})
}