Unverified Commit 4420281d authored by zxhlyh's avatar zxhlyh Committed by GitHub

Feat/segment add tag (#907)

parent d9afebe2
import { useState } from 'react'
import type { ChangeEvent, FC, KeyboardEvent } from 'react'
import {} from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import AutosizeInput from 'react-18-input-autosize'
import { X } from '@/app/components/base/icons/src/vender/line/general'
import { useToastContext } from '@/app/components/base/toast'
type TagInputProps = {
items: string[]
onChange: (items: string[]) => void
disableRemove?: boolean
disableAdd?: boolean
}
const TagInput: FC<TagInputProps> = ({
items,
onChange,
disableAdd,
disableRemove,
}) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const [value, setValue] = useState('')
const [focused, setFocused] = useState(false)
const handleRemove = (index: number) => {
const copyItems = [...items]
copyItems.splice(index, 1)
onChange(copyItems)
}
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
const valueTrimed = value.trim()
if (!valueTrimed || (items.find(item => item === valueTrimed)))
return
if (valueTrimed.length > 20) {
notify({ type: 'error', message: t('datasetDocuments.segment.keywordError') })
return
}
onChange([...items, valueTrimed])
setValue('')
}
}
const handleBlur = () => {
setValue('')
setFocused(false)
}
return (
<div className='flex flex-wrap'>
{
items.map((item, index) => (
<div
key={item}
className='flex items-center mr-1 mt-1 px-2 py-1 text-sm text-gray-700 rounded-lg border border-gray-200'>
{item}
{
!disableRemove && (
<X
className='ml-0.5 w-3 h-3 text-gray-500 cursor-pointer'
onClick={() => handleRemove(index)}
/>
)
}
</div>
))
}
{
!disableAdd && (
<AutosizeInput
inputClassName='outline-none appearance-none placeholder:text-gray-300 caret-primary-600 hover:placeholder:text-gray-400'
className={`
mt-1 py-1 rounded-lg border border-transparent text-sm max-w-[300px] overflow-hidden
${focused && 'px-2 border !border-dashed !border-gray-200'}
`}
onFocus={() => setFocused(true)}
onBlur={handleBlur}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={t('datasetDocuments.segment.addKeyWord')}
/>
)
}
</div>
)
}
export default TagInput
...@@ -26,6 +26,7 @@ import { Edit03, XClose } from '@/app/components/base/icons/src/vender/line/gene ...@@ -26,6 +26,7 @@ import { Edit03, XClose } from '@/app/components/base/icons/src/vender/line/gene
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common' import AutoHeightTextarea from '@/app/components/base/auto-height-textarea/common'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal' import NewSegmentModal from '@/app/components/datasets/documents/detail/new-segment-modal'
import TagInput from '@/app/components/base/tag-input'
export const SegmentIndexTag: FC<{ positionId: string | number; className?: string }> = ({ positionId, className }) => { export const SegmentIndexTag: FC<{ positionId: string | number; className?: string }> = ({ positionId, className }) => {
const localPositionId = useMemo(() => { const localPositionId = useMemo(() => {
...@@ -45,7 +46,7 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri ...@@ -45,7 +46,7 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
type ISegmentDetailProps = { type ISegmentDetailProps = {
segInfo?: Partial<SegmentDetailModel> & { id: string } segInfo?: Partial<SegmentDetailModel> & { id: string }
onChangeSwitch?: (segId: string, enabled: boolean) => Promise<void> onChangeSwitch?: (segId: string, enabled: boolean) => Promise<void>
onUpdate: (segmentId: string, q: string, a: string) => void onUpdate: (segmentId: string, q: string, a: string, k: string[]) => void
onCancel: () => void onCancel: () => void
} }
/** /**
...@@ -61,14 +62,16 @@ export const SegmentDetail: FC<ISegmentDetailProps> = memo(({ ...@@ -61,14 +62,16 @@ export const SegmentDetail: FC<ISegmentDetailProps> = memo(({
const [isEditing, setIsEditing] = useState(false) const [isEditing, setIsEditing] = useState(false)
const [question, setQuestion] = useState(segInfo?.content || '') const [question, setQuestion] = useState(segInfo?.content || '')
const [answer, setAnswer] = useState(segInfo?.answer || '') const [answer, setAnswer] = useState(segInfo?.answer || '')
const [keywords, setKeywords] = useState<string[]>(segInfo?.keywords || [])
const handleCancel = () => { const handleCancel = () => {
setIsEditing(false) setIsEditing(false)
setQuestion(segInfo?.content || '') setQuestion(segInfo?.content || '')
setAnswer(segInfo?.answer || '') setAnswer(segInfo?.answer || '')
setKeywords(segInfo?.keywords || [])
} }
const handleSave = () => { const handleSave = () => {
onUpdate(segInfo?.id || '', question, answer) onUpdate(segInfo?.id || '', question, answer, keywords)
} }
const renderContent = () => { const renderContent = () => {
...@@ -148,9 +151,15 @@ export const SegmentDetail: FC<ISegmentDetailProps> = memo(({ ...@@ -148,9 +151,15 @@ export const SegmentDetail: FC<ISegmentDetailProps> = memo(({
<div className={s.keywordWrapper}> <div className={s.keywordWrapper}>
{!segInfo?.keywords?.length {!segInfo?.keywords?.length
? '-' ? '-'
: segInfo?.keywords?.map((word: any) => { : (
return <div className={s.keyword}>{word}</div> <TagInput
})} items={keywords}
onChange={newKeywords => setKeywords(newKeywords)}
disableAdd={!isEditing}
disableRemove={!isEditing || (keywords.length === 1)}
/>
)
}
</div> </div>
<div className={cn(s.footer, s.numberInfo)}> <div className={cn(s.footer, s.numberInfo)}>
<div className='flex items-center'> <div className='flex items-center'>
...@@ -272,7 +281,7 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal ...@@ -272,7 +281,7 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal
} }
} }
const handleUpdateSegment = async (segmentId: string, question: string, answer: string) => { const handleUpdateSegment = async (segmentId: string, question: string, answer: string, keywords: string[]) => {
const params: SegmentUpdator = { content: '' } const params: SegmentUpdator = { content: '' }
if (docForm === 'qa_model') { if (docForm === 'qa_model') {
if (!question.trim()) if (!question.trim())
...@@ -290,6 +299,9 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal ...@@ -290,6 +299,9 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal
params.content = question params.content = question
} }
if (keywords.length)
params.keywords = keywords
const res = await updateSegment({ datasetId, documentId, segmentId, body: params }) const res = await updateSegment({ datasetId, documentId, segmentId, body: params })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
onCloseModal() onCloseModal()
...@@ -298,6 +310,7 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal ...@@ -298,6 +310,7 @@ const Completed: FC<ICompletedProps> = ({ showNewSegmentModal, onNewSegmentModal
if (seg.id === segmentId) { if (seg.id === segmentId) {
seg.answer = res.data.answer seg.answer = res.data.answer
seg.content = res.data.content seg.content = res.data.content
seg.keywords = res.data.keywords
seg.word_count = res.data.word_count seg.word_count = res.data.word_count
seg.hit_count = res.data.hit_count seg.hit_count = res.data.hit_count
seg.index_node_hash = res.data.index_node_hash seg.index_node_hash = res.data.index_node_hash
......
...@@ -10,6 +10,7 @@ import { Hash02, XClose } from '@/app/components/base/icons/src/vender/line/gene ...@@ -10,6 +10,7 @@ import { Hash02, XClose } from '@/app/components/base/icons/src/vender/line/gene
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import type { SegmentUpdator } from '@/models/datasets' import type { SegmentUpdator } from '@/models/datasets'
import { addSegment } from '@/service/datasets' import { addSegment } from '@/service/datasets'
import TagInput from '@/app/components/base/tag-input'
type NewSegmentModalProps = { type NewSegmentModalProps = {
isShow: boolean isShow: boolean
...@@ -29,11 +30,13 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({ ...@@ -29,11 +30,13 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({
const [question, setQuestion] = useState('') const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('') const [answer, setAnswer] = useState('')
const { datasetId, documentId } = useParams() const { datasetId, documentId } = useParams()
const [keywords, setKeywords] = useState<string[]>([])
const handleCancel = () => { const handleCancel = () => {
setQuestion('') setQuestion('')
setAnswer('') setAnswer('')
onCancel() onCancel()
setKeywords([])
} }
const handleSave = async () => { const handleSave = async () => {
...@@ -54,6 +57,9 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({ ...@@ -54,6 +57,9 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({
params.content = question params.content = question
} }
if (keywords?.length)
params.keywords = keywords
await addSegment({ datasetId, documentId, body: params }) await addSegment({ datasetId, documentId, body: params })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
handleCancel() handleCancel()
...@@ -117,8 +123,10 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({ ...@@ -117,8 +123,10 @@ const NewSegmentModal: FC<NewSegmentModalProps> = memo(({
</span> </span>
</div> </div>
<div className='mb-4 py-1.5 h-[420px] overflow-auto'>{renderContent()}</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='text-xs font-medium text-gray-500'>{t('datasetDocuments.segment.keywords')}</div>
<div className='mb-8'></div> <div className='mb-8'>
<TagInput items={keywords} onChange={newKeywords => setKeywords(newKeywords)} />
</div>
<div className='flex justify-end'> <div className='flex justify-end'>
<Button <Button
className='mr-2 !h-9 !px-4 !py-2 text-sm font-medium text-gray-700 !rounded-lg' className='mr-2 !h-9 !px-4 !py-2 text-sm font-medium text-gray-700 !rounded-lg'
......
declare module 'lamejs'; declare module 'lamejs';
declare module 'react-18-input-autosize';
\ No newline at end of file
...@@ -308,6 +308,8 @@ const translation = { ...@@ -308,6 +308,8 @@ const translation = {
segment: { segment: {
paragraphs: 'Paragraphs', paragraphs: 'Paragraphs',
keywords: 'Key Words', keywords: 'Key Words',
addKeyWord: 'Add key word',
keywordError: 'The maximum length of keyword is 20',
characters: 'characters', characters: 'characters',
hitCount: 'hit count', hitCount: 'hit count',
vectorHash: 'Vector hash: ', vectorHash: 'Vector hash: ',
......
...@@ -307,6 +307,8 @@ const translation = { ...@@ -307,6 +307,8 @@ const translation = {
segment: { segment: {
paragraphs: '段落', paragraphs: '段落',
keywords: '关键词', keywords: '关键词',
addKeyWord: '添加关键词',
keywordError: '关键词最大长度为 20',
characters: '字符', characters: '字符',
hitCount: '命中次数', hitCount: '命中次数',
vectorHash: '向量哈希:', vectorHash: '向量哈希:',
......
...@@ -388,4 +388,5 @@ export type RelatedAppResponse = { ...@@ -388,4 +388,5 @@ export type RelatedAppResponse = {
export type SegmentUpdator = { export type SegmentUpdator = {
content: string content: string
answer?: string answer?: string
keywords?: string[]
} }
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
"next": "13.3.1", "next": "13.3.1",
"qs": "^6.11.1", "qs": "^6.11.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-18-input-autosize": "^3.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.2", "react-error-boundary": "^4.0.2",
"react-headless-pagination": "^1.1.4", "react-headless-pagination": "^1.1.4",
...@@ -80,8 +81,8 @@ ...@@ -80,8 +81,8 @@
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/js-cookie": "^3.0.3", "@types/js-cookie": "^3.0.3",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "18.15.0",
"@types/negotiator": "^0.6.1", "@types/negotiator": "^0.6.1",
"@types/node": "18.15.0",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
......
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