Commit 82b727cf authored by StyleZhang's avatar StyleZhang

add new segment

parent 49c2309b
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="hash-02">
<path id="Icon" d="M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25" stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "hash-02"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25",
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Hash02"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Hash02.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as Check } from './Check' export { default as Check } from './Check'
export { default as Edit03 } from './Edit03' export { default as Edit03 } from './Edit03'
export { default as Hash02 } from './Hash02'
export { default as Loading02 } from './Loading02' export { default as Loading02 } from './Loading02'
export { default as LogOut01 } from './LogOut01' export { default as LogOut01 } from './LogOut01'
export { default as Trash03 } from './Trash03' export { default as Trash03 } from './Trash03'
......
...@@ -100,6 +100,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => { ...@@ -100,6 +100,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
enabled: documentDetail?.enabled || false, enabled: documentDetail?.enabled || false,
archived: documentDetail?.archived || false, archived: documentDetail?.archived || false,
id: documentId, id: documentId,
doc_form: documentDetail?.doc_form || '',
}} }}
datasetId={datasetId} datasetId={datasetId}
onUpdate={handleOperate} onUpdate={handleOperate}
......
import { memo, useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { useParams } from 'next/navigation'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
import { Hash02, XClose } from '@/app/components/base/icons/src/vender/line/general'
import { ToastContext } from '@/app/components/base/toast'
import type { SegmentUpdator } from '@/models/datasets'
import { addSegment } from '@/service/datasets'
type NewSegmentModalProps = {
isShow: boolean
onCancel: () => void
docForm: string
onSave: () => void
}
const NewSegmentModal: FC<NewSegmentModalProps> = memo(({
isShow,
onCancel,
docForm,
onSave,
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const { datasetId, documentId } = useParams()
const handleSave = async () => {
const params: SegmentUpdator = { content: '' }
if (docForm === 'qa_model') {
if (!question.trim())
return notify({ type: 'error', message: t('datasetDocuments.segment.questionEmpty') })
if (!answer.trim())
return notify({ type: 'error', message: t('datasetDocuments.segment.answerEmpty') })
params.content = question
params.answer = answer
}
else {
if (!question.trim())
return notify({ type: 'error', message: t('datasetDocuments.segment.contentEmpty') })
params.content = question
}
await addSegment({ datasetId, documentId, body: params })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
onCancel()
onSave()
}
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='leading-6 text-md text-gray-800'
value={question}
placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
onChange={e => setQuestion(e.target.value)}
autoFocus
/>
)
}
return (
<Modal isShow={isShow} onClose={() => {}} className='pt-8 px-8 pb-6 !max-w-[640px] !rounded-xl'>
<div className={'flex flex-col relative'}>
<div className='absolute right-0 -top-0.5 flex items-center h-6'>
<div className='flex justify-center items-center w-6 h-6 cursor-pointer' onClick={onCancel}>
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div>
<div className='mb-[14px]'>
<span className='inline-flex items-center px-1.5 h-5 border border-gray-200 rounded-md'>
<Hash02 className='mr-0.5 w-3 h-3 text-gray-400' />
<span className='text-[11px] font-medium text-gray-500 italic'>
{
docForm === 'qa_model'
? t('datasetDocuments.segment.newQaSegment')
: t('datasetDocuments.segment.newTextSegment')
}
</span>
</span>
</div>
<div className='mb-4 py-1.5 h-[420px] overflow-auto'>{renderContent()}</div>
<div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetDocuments.segment.keywords')}</div>
<div className='mb-8'></div>
<div className='flex justify-end'>
<Button
className='mr-2 !h-9 !px-4 !py-2 text-sm font-medium text-gray-700 !rounded-lg'
onClick={onCancel}>
{t('common.operation.cancel')}
</Button>
<Button
type='primary'
className='!h-9 !px-4 !py-2 text-sm font-medium !rounded-lg'
onClick={handleSave}>
{t('common.operation.save')}
</Button>
</div>
</div>
</Modal>
)
})
export default NewSegmentModal
...@@ -27,6 +27,8 @@ import NotionIcon from '@/app/components/base/notion-icon' ...@@ -27,6 +27,8 @@ import NotionIcon from '@/app/components/base/notion-icon'
import ProgressBar from '@/app/components/base/progress-bar' import ProgressBar from '@/app/components/base/progress-bar'
import { DataSourceType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets' import { DataSourceType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets'
import type { CommonResponse } from '@/models/common' import type { CommonResponse } from '@/models/common'
import { FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal'
export const SettingsIcon: FC<{ className?: string }> = ({ className }) => { export const SettingsIcon: FC<{ className?: string }> = ({ className }) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
...@@ -94,14 +96,16 @@ export const OperationAction: FC<{ ...@@ -94,14 +96,16 @@ export const OperationAction: FC<{
archived: boolean archived: boolean
id: string id: string
data_source_type: string data_source_type: string
doc_form: string
} }
datasetId: string datasetId: string
onUpdate: (operationName?: string) => void onUpdate: (operationName?: string) => void
scene?: 'list' | 'detail' scene?: 'list' | 'detail'
className?: string className?: string
}> = ({ datasetId, detail, onUpdate, scene = 'list', className = '' }) => { }> = ({ datasetId, detail, onUpdate, scene = 'list', className = '' }) => {
const { id, enabled = false, archived = false, data_source_type } = detail || {} const { id, enabled = false, archived = false, data_source_type, doc_form } = detail || {}
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [showNewSegmentModal, setShowNewSegmentModal] = useState(false)
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
...@@ -185,6 +189,14 @@ export const OperationAction: FC<{ ...@@ -185,6 +189,14 @@ export const OperationAction: FC<{
<SettingsIcon /> <SettingsIcon />
<span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span> <span className={s.actionName}>{t('datasetDocuments.list.action.settings')}</span>
</div> </div>
{
!isListScene && (
<div className={s.actionItem} onClick={() => setShowNewSegmentModal(true)}>
<FilePlus02 className='w-4 h-4 text-gray-500' />
<span className={s.actionName}>{t('datasetDocuments.list.action.add')}</span>
</div>
)
}
{ {
data_source_type === 'notion_import' && ( data_source_type === 'notion_import' && (
<div className={s.actionItem} onClick={() => onOperate('sync')}> <div className={s.actionItem} onClick={() => onOperate('sync')}>
...@@ -231,6 +243,12 @@ export const OperationAction: FC<{ ...@@ -231,6 +243,12 @@ export const OperationAction: FC<{
</div> </div>
</div> </div>
</Modal>} </Modal>}
<NewSegmentModal
isShow={showNewSegmentModal}
onCancel={() => setShowNewSegmentModal(false)}
docForm={doc_form}
onSave={() => {}}
/>
</div> </div>
} }
...@@ -339,7 +357,7 @@ const DocumentList: FC<IDocumentListProps> = ({ documents = [], datasetId, onUpd ...@@ -339,7 +357,7 @@ const DocumentList: FC<IDocumentListProps> = ({ documents = [], datasetId, onUpd
<td> <td>
<OperationAction <OperationAction
datasetId={datasetId} datasetId={datasetId}
detail={pick(doc, ['enabled', 'archived', 'id', 'data_source_type'])} detail={pick(doc, ['enabled', 'archived', 'id', 'data_source_type', 'doc_form'])}
onUpdate={onUpdate} onUpdate={onUpdate}
/> />
</td> </td>
......
...@@ -17,6 +17,7 @@ const translation = { ...@@ -17,6 +17,7 @@ const translation = {
action: { action: {
uploadFile: 'Upload new file', uploadFile: 'Upload new file',
settings: 'Segment settings', settings: 'Segment settings',
add: 'Add new segment',
archive: 'Archive', archive: 'Archive',
delete: 'Delete', delete: 'Delete',
enableWarning: 'Archived file cannot be enabled', enableWarning: 'Archived file cannot be enabled',
...@@ -316,6 +317,8 @@ const translation = { ...@@ -316,6 +317,8 @@ const translation = {
answerEmpty: 'Answer can not be empty', answerEmpty: 'Answer can not be empty',
contentPlaceholder: 'add content here', contentPlaceholder: 'add content here',
contentEmpty: 'Content can not be empty', contentEmpty: 'Content can not be empty',
newTextSegment: 'New Text Segment',
newQaSegment: 'New Q&A Segment',
}, },
} }
......
...@@ -17,6 +17,7 @@ const translation = { ...@@ -17,6 +17,7 @@ const translation = {
action: { action: {
uploadFile: '上传新文件', uploadFile: '上传新文件',
settings: '分段设置', settings: '分段设置',
add: '添加新分段',
archive: '归档', archive: '归档',
delete: '删除', delete: '删除',
enableWarning: '归档的文件无法启用', enableWarning: '归档的文件无法启用',
...@@ -315,6 +316,8 @@ const translation = { ...@@ -315,6 +316,8 @@ const translation = {
answerEmpty: '答案不能为空', answerEmpty: '答案不能为空',
contentPlaceholder: '在这里添加内容', contentPlaceholder: '在这里添加内容',
contentEmpty: '内容不能为空', contentEmpty: '内容不能为空',
newTextSegment: '新文本分段',
newQaSegment: '新问答分段',
}, },
} }
......
...@@ -160,6 +160,10 @@ export const updateSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string ...@@ -160,6 +160,10 @@ export const updateSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string
return patch(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) as Promise<{ data: SegmentDetailModel; doc_form: string }> return patch(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) as Promise<{ data: SegmentDetailModel; doc_form: string }>
} }
export const addSegment: Fetcher<{ data: SegmentDetailModel; doc_form: string }, { datasetId: string; documentId: string; body: SegmentUpdator }> = ({ datasetId, documentId, body }) => {
return post(`/datasets/${datasetId}/documents/${documentId}/segment`, { body }) as Promise<{ data: SegmentDetailModel; doc_form: string }>
}
// hit testing // hit testing
export const hitTesting: Fetcher<HitTestingResponse, { datasetId: string; queryText: string }> = ({ datasetId, queryText }) => { export const hitTesting: Fetcher<HitTestingResponse, { datasetId: string; queryText: string }> = ({ datasetId, queryText }) => {
return post(`/datasets/${datasetId}/hit-testing`, { body: { query: queryText } }) as Promise<HitTestingResponse> return post(`/datasets/${datasetId}/hit-testing`, { body: { query: queryText } }) as Promise<HitTestingResponse>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment