feat: add functionality to regenerate child chunks and enhance UI components for segment management

This commit is contained in:
twwu
2024-12-10 18:01:38 +08:00
parent 6055e27050
commit b4a6ec077f
10 changed files with 312 additions and 234 deletions

View File

@@ -0,0 +1,81 @@
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: (needRegenerate: boolean) => void
loading: boolean
actionType?: 'edit' | 'add'
}
const ActionButtons: FC<IActionButtonsProps> = ({
handleCancel,
handleSave,
loading,
actionType = 'edit',
}) => {
const { t } = useTranslation()
const [mode, parentMode] = useDocumentContext(s => [s.mode, s.parentMode])
useKeyPress(['esc'], (e) => {
e.preventDefault()
handleCancel()
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
if (loading)
return
e.preventDefault()
handleSave(false)
}
, { 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')
? <Button
onClick={handleSave.bind(null, true)}
disabled={loading}
>
<span className='text-components-button-secondary-text system-sm-medium'>
{t('datasetDocuments.segment.saveAndRegenerate')}
</span>
</Button>
: null
}
<Button
variant='primary'
onClick={handleSave.bind(null, false)}
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

@@ -0,0 +1,32 @@
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

@@ -0,0 +1,64 @@
import React, { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
type IChunkContentProps = {
question: string
answer: string
onQuestionChange: (question: string) => void
onAnswerChange: (answer: string) => void
isEditMode?: boolean
docForm: string
}
const ChunkContent: FC<IChunkContentProps> = ({
question,
answer,
onQuestionChange,
onAnswerChange,
isEditMode,
docForm,
}) => {
const { t } = useTranslation()
if (docForm === 'qa_model') {
return (
<>
<div className='mb-1 text-xs font-medium text-gray-500'>QUESTION</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={question}
placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''}
onChange={e => onQuestionChange(e.target.value)}
disabled={!isEditMode}
/>
<div className='mb-1 text-xs font-medium text-gray-500'>ANSWER</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={answer}
placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''}
onChange={e => onAnswerChange(e.target.value)}
disabled={!isEditMode}
autoFocus
/>
</>
)
}
return (
<AutoHeightTextarea
className='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

@@ -0,0 +1,47 @@
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

@@ -51,7 +51,7 @@ export const useSegmentListContext = (selector: (value: SegmentListContextValue)
return useContextSelector(SegmentListContext, selector)
}
export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string; className?: string }> = ({ positionId, label, className }) => {
export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string; className?: string }> = React.memo(({ positionId, label, className }) => {
const localPositionId = useMemo(() => {
const positionIdStr = String(positionId)
if (positionIdStr.length >= 3)
@@ -66,7 +66,9 @@ export const SegmentIndexTag: FC<{ positionId?: string | number; label?: string;
</div>
</div>
)
}
})
SegmentIndexTag.displayName = 'SegmentIndexTag'
type ICompletedProps = {
embeddingAvailable: boolean
@@ -240,7 +242,13 @@ const Completed: FC<ICompletedProps> = ({
)
}, [])
const handleUpdateSegment = async (segmentId: string, question: string, answer: string, keywords: string[]) => {
const handleUpdateSegment = async (
segmentId: string,
question: string,
answer: string,
keywords: string[],
needRegenerate: boolean,
) => {
const params: SegmentUpdater = { content: '' }
if (docForm === 'qa_model') {
if (!question.trim())
@@ -261,6 +269,9 @@ const Completed: FC<ICompletedProps> = ({
if (keywords.length)
params.keywords = keywords
if (needRegenerate)
params.regenerate_child_chunks = needRegenerate
try {
eventEmitter?.emit('update-segment')
const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
@@ -275,6 +286,7 @@ const Completed: FC<ICompletedProps> = ({
seg.hit_count = res.data.hit_count
seg.enabled = res.data.enabled
seg.updated_at = res.data.updated_at
seg.child_chunks = res.data.child_chunks
}
}
setSegments([...segments])
@@ -318,12 +330,13 @@ const Completed: FC<ICompletedProps> = ({
const total = segmentListData?.total || 0
const newPage = Math.ceil((total + 1) / limit)
needScrollToBottom.current = true
if (newPage > totalPages)
if (newPage > totalPages) {
setCurrentPage(totalPages + 1)
else if (currentPage === totalPages)
}
else {
resetList()
else
setCurrentPage(totalPages)
currentPage !== totalPages && setCurrentPage(totalPages)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [segmentListData, limit, currentPage])
@@ -407,6 +420,7 @@ const Completed: FC<ICompletedProps> = ({
>
<SegmentDetail
segInfo={currSegment.segInfo ?? { id: '' }}
docForm={docForm}
isEditMode={currSegment.isEditMode}
onUpdate={handleUpdateSegment}
onCancel={onCloseDrawer}

View File

@@ -4,23 +4,23 @@ import {
RiCloseLine,
RiExpandDiagonalLine,
} from '@remixicon/react'
import { useKeyPress } from 'ahooks'
import { useDocumentContext } from '../index'
import ActionButtons from './common/action-buttons'
import ChunkContent from './common/chunk-content'
import Keywords from './common/keywords'
import { SegmentIndexTag, useSegmentListContext } from './index'
import type { SegmentDetailModel } from '@/models/datasets'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
import Button from '@/app/components/base/button'
import TagInput from '@/app/components/base/tag-input'
import { formatNumber } from '@/utils/format'
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import classNames from '@/utils/classnames'
import Divider from '@/app/components/base/divider'
type ISegmentDetailProps = {
segInfo?: Partial<SegmentDetailModel> & { id: string }
onUpdate: (segmentId: string, q: string, a: string, k: string[]) => void
onUpdate: (segmentId: string, q: string, a: string, k: string[], needRegenerate: boolean) => void
onCancel: () => void
isEditMode?: boolean
docForm: string
}
/**
@@ -31,6 +31,7 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
onUpdate,
onCancel,
isEditMode,
docForm,
}) => {
const { t } = useTranslation()
const [question, setQuestion] = useState(segInfo?.content || '')
@@ -39,6 +40,7 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
const { eventEmitter } = useEventEmitterContextContext()
const [loading, setLoading] = useState(false)
const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen])
const [mode] = useDocumentContext(s => s.mode)
eventEmitter?.useSubscription((v) => {
if (v === 'update-segment')
@@ -53,110 +55,9 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
setAnswer(segInfo?.answer || '')
setKeywords(segInfo?.keywords || [])
}
const handleSave = () => {
onUpdate(segInfo?.id || '', question, answer, keywords)
}
useKeyPress(['esc'], (e) => {
e.preventDefault()
handleCancel()
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
if (loading)
return
e.preventDefault()
handleSave()
}
, { exactMatch: true, useCapture: true })
const renderContent = () => {
if (segInfo?.answer) {
return (
<>
<div className='mb-1 text-xs font-medium text-gray-500'>QUESTION</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={question}
placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''}
onChange={e => setQuestion(e.target.value)}
disabled={!isEditMode}
/>
<div className='mb-1 text-xs font-medium text-gray-500'>ANSWER</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={answer}
placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''}
onChange={e => setAnswer(e.target.value)}
disabled={!isEditMode}
autoFocus
/>
</>
)
}
return (
<AutoHeightTextarea
className='body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
value={question}
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
onChange={e => setQuestion(e.target.value)}
disabled={!isEditMode}
autoFocus
/>
)
}
const renderActionButtons = () => {
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>
<Button
variant='primary'
onClick={handleSave}
disabled={loading}
loading={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>
)
}
const renderKeywords = () => {
return (
<div className={classNames('flex flex-col', fullScreen ? 'w-1/5' : '')}>
<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
? '-'
: (
<TagInput
items={keywords}
onChange={newKeywords => setKeywords(newKeywords)}
disableAdd={!isEditMode}
disableRemove={!isEditMode || (keywords.length === 1)}
/>
)
}
</div>
</div>
)
const handleSave = (needRegenerate = false) => {
onUpdate(segInfo?.id || '', question, answer, keywords, needRegenerate)
}
return (
@@ -173,7 +74,7 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
<div className='flex items-center'>
{isEditMode && fullScreen && (
<>
{renderActionButtons()}
<ActionButtons handleCancel={handleCancel} handleSave={handleSave} loading={loading} />
<Divider type='vertical' className='h-3.5 bg-divider-regular ml-4 mr-2' />
</>
)}
@@ -187,13 +88,27 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
</div>
<div className={classNames('flex grow overflow-hidden', fullScreen ? 'w-full flex-row justify-center px-6 pt-6 gap-x-8 mx-auto' : 'flex-col gap-y-1 py-3 px-4')}>
<div className={classNames('break-all overflow-y-auto whitespace-pre-line', fullScreen ? 'w-1/2' : 'grow')}>
{renderContent()}
<ChunkContent
docForm={docForm}
question={question}
answer={answer}
onQuestionChange={question => setQuestion(question)}
onAnswerChange={answer => setAnswer(answer)}
isEditMode={isEditMode}
/>
</div>
{renderKeywords()}
{mode === 'custom' && <Keywords
className={fullScreen ? 'w-1/5' : ''}
actionType={isEditMode ? 'edit' : 'view'}
segInfo={segInfo}
keywords={keywords}
isEditMode={isEditMode}
onKeywordsChange={keywords => setKeywords(keywords)}
/>}
</div>
{isEditMode && !fullScreen && (
<div className='flex items-center justify-end p-4 pt-3 border-t-[1px] border-t-divider-subtle'>
{renderActionButtons()}
<ActionButtons handleCancel={handleCancel} handleSave={handleSave} loading={loading} />
</div>
)}
</div>

View File

@@ -4,21 +4,20 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { useParams } from 'next/navigation'
import { RiCloseLine, RiExpandDiagonalLine } from '@remixicon/react'
import { useKeyPress } from 'ahooks'
import { useShallow } from 'zustand/react/shallow'
import { SegmentIndexTag, useSegmentListContext } from './completed'
import ActionButtons from './completed/common/action-buttons'
import Keywords from './completed/common/keywords'
import ChunkContent from './completed/common/chunk-content'
import AddAnother from './completed/common/add-another'
import { useDocumentContext } from './index'
import { useStore as useAppStore } from '@/app/components/app/store'
import Button from '@/app/components/base/button'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
import { ToastContext } from '@/app/components/base/toast'
import type { SegmentUpdater } from '@/models/datasets'
import { addSegment } from '@/service/datasets'
import TagInput from '@/app/components/base/tag-input'
import classNames from '@/utils/classnames'
import { formatNumber } from '@/utils/format'
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import Divider from '@/app/components/base/divider'
import Checkbox from '@/app/components/base/checkbox'
type NewSegmentModalProps = {
onCancel: () => void
@@ -40,8 +39,9 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
const { datasetId, documentId } = useParams<{ datasetId: string; documentId: string }>()
const [keywords, setKeywords] = useState<string[]>([])
const [loading, setLoading] = useState(false)
const [addAnother, setAnother] = useState(true)
const [addAnother, setAddAnother] = useState(true)
const [fullScreen, toggleFullScreen] = useSegmentListContext(s => [s.fullScreen, s.toggleFullScreen])
const [mode] = useDocumentContext(s => s.mode)
const { appSidebarExpand } = useAppStore(useShallow(state => ({
appSidebarExpand: state.appSidebarExpand,
})))
@@ -106,107 +106,6 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
}
}
useKeyPress(['esc'], (e) => {
e.preventDefault()
handleCancel()
})
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.s`, (e) => {
if (loading)
return
e.preventDefault()
handleSave()
}
, { exactMatch: true, useCapture: true })
const renderContent = () => {
if (docForm === 'qa_model') {
return (
<>
<div className='mb-1 text-xs font-medium text-gray-500'>QUESTION</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={question}
placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''}
onChange={e => setQuestion(e.target.value)}
autoFocus
/>
<div className='mb-1 text-xs font-medium text-gray-500'>ANSWER</div>
<AutoHeightTextarea
outerClassName='mb-4'
className='leading-6 text-md text-gray-800'
value={answer}
placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''}
onChange={e => setAnswer(e.target.value)}
/>
</>
)
}
return (
<AutoHeightTextarea
className='body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
value={question}
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
onChange={e => setQuestion(e.target.value)}
autoFocus
/>
)
}
const renderActionButtons = () => {
return (
<div className='flex items-center gap-x-2'>
<Button
className='flex items-center gap-x-1'
onClick={handleCancel.bind(null, 'esc')}
>
<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>
</Button>
<Button
className='flex items-center gap-x-1'
variant='primary'
onClick={handleSave}
disabled={loading}
loading={loading}
>
<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>
</Button>
</div>
)
}
const AddAnotherCheckBox = () => {
return (
<div className={classNames('flex items-center gap-x-1 pl-1', fullScreen ? 'mr-3' : '')}>
<Checkbox
key='add-another-checkbox'
className='shrink-0'
checked={addAnother}
onCheck={() => setAnother(!addAnother)}
/>
<span className='text-text-tertiary system-xs-medium'>{t('datasetDocuments.segment.addAnother')}</span>
</div>
)
}
const renderKeywords = () => {
return (
<div className={classNames('flex flex-col', fullScreen ? 'w-1/5' : '')}>
<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'>
<TagInput items={keywords} onChange={newKeywords => setKeywords(newKeywords)} />
</div>
</div>
)
}
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')}>
@@ -225,8 +124,13 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
<div className='flex items-center'>
{fullScreen && (
<>
{AddAnotherCheckBox()}
{renderActionButtons()}
<AddAnother className='mr-3' isChecked={addAnother} onCheck={() => setAddAnother(!addAnother)} />
<ActionButtons
handleCancel={handleCancel.bind(null, 'esc')}
handleSave={handleSave}
loading={loading}
actionType='add'
/>
<Divider type='vertical' className='h-3.5 bg-divider-regular ml-4 mr-2' />
</>
)}
@@ -240,14 +144,32 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
</div>
<div className={classNames('flex grow overflow-hidden', fullScreen ? 'w-full flex-row justify-center px-6 pt-6 gap-x-8' : 'flex-col gap-y-1 py-3 px-4')}>
<div className={classNames('break-all overflow-y-auto whitespace-pre-line', fullScreen ? 'w-1/2' : 'grow')}>
{renderContent()}
<ChunkContent
docForm={docForm}
question={question}
answer={answer}
onQuestionChange={question => setQuestion(question)}
onAnswerChange={answer => setAnswer(answer)}
isEditMode={true}
/>
</div>
{renderKeywords()}
{mode === 'custom' && <Keywords
className={fullScreen ? 'w-1/5' : ''}
actionType='add'
keywords={keywords}
isEditMode={true}
onKeywordsChange={keywords => setKeywords(keywords)}
/>}
</div>
{!fullScreen && (
<div className='flex items-center justify-between p-4 pt-3 border-t-[1px] border-t-divider-subtle'>
{AddAnotherCheckBox()}
{renderActionButtons()}
<AddAnother isChecked={addAnother} onCheck={() => setAddAnother(!addAnother)} />
<ActionButtons
handleCancel={handleCancel.bind(null, 'esc')}
handleSave={handleSave}
loading={loading}
actionType='add'
/>
</div>
)}
</div>

View File

@@ -353,6 +353,7 @@ const translation = {
delete: 'Delete this chunk ?',
chunkAdded: '1 chunk added',
viewAddedChunk: 'View',
saveAndRegenerate: 'Save & Regenerate Child Chunks',
},
}

View File

@@ -351,6 +351,7 @@ const translation = {
delete: '删除这个分段?',
chunkAdded: '新增一个分段',
viewAddedChunk: '查看',
saveAndRegenerate: '保存并重新生成子分段',
},
}

View File

@@ -561,6 +561,7 @@ export type SegmentUpdater = {
content: string
answer?: string
keywords?: string[]
regenerate_child_chunks?: boolean
}
export enum DocForm {