123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- 'use client'
- import React, { useEffect, useState } from 'react'
- import { RiCloseLine } from '@remixicon/react'
- import Modal from '@/app/components/base/modal'
- import Button from '@/app/components/base/button'
- import {
- addCorpus, addCorpusQuestion, delBatchCorpusQuestion, editCorpus as editCorpusFunc, editCorpusQuestion, fetchCorpusQuestion,
- fetchIntent,
- getCorpus,
- } from '@/service/common'
- import 'react-multi-email/dist/style.css'
- import Input from '@/app/components/base/input'
- import Checkbox from '@/app/components/base/checkbox'
- import cn from '@/utils/classnames'
- import { useContext } from 'use-context-selector'
- import { ToastContext } from '@/app/components/base/toast'
- import Confirm from '@/app/components/base/confirm'
- import { Cascader as AntdCascader } from 'antd'
- import { Textarea } from '@/app/components/base/textarea'
- const DetailModal = ({
- transfer,
- onCancel,
- onSend,
- onRefresh,
- }: any) => {
- const { notify } = useContext(ToastContext)
- const [intentCascader, setIntentCascader] = useState<any>([])
- useEffect(() => {
- fetchIntent({
- url: '/intentions',
- params: {
- page: 1,
- limit: 99999,
- },
- }).then((res: any) => {
- const map = new Map()
- res.data.forEach((v: any) => {
- if (map.has(v.type_id)) {
- const parent = map.get(v.type_id)
- parent.children.push(v)
- map.set(v.type_id, parent)
- }
- else {
- map.set(v.type_id, {
- id: v.type_id,
- name: v.type_name,
- children: [v],
- })
- }
- })
- setIntentCascader(Array.from(map.values()))
- })
- }, [])
- const [question, setQuestion] = useState<string>('')
- const [intentValue, setIntentValue] = useState<any>([])
- const [questionCorpus, setQuestionCorpus] = useState<any>('')
- const [similarityList, setSimilarityList] = useState<any>([])
- const handleSave = async () => {
- try {
- let res
- if (transfer.mode === 'add') {
- res = await addCorpus({
- url: '/intentions/corpus',
- body: { question, intention_id: intentValue[intentValue.length - 1] },
- })
- }
- else {
- res = await editCorpusFunc({
- url: `/intentions/corpus/${transfer.row.id}`,
- body: { question, intention_id: intentValue[intentValue.length - 1], question_config: questionCorpus },
- })
- }
- const { id }: any = res
- if (id) {
- if (transfer.mode === 'add')
- onRefresh(id)
- else
- onSend()
- }
- }
- catch (e) { }
- }
- useEffect(() => {
- if (transfer.row?.id) {
- getCorpus({ url: `/intentions/corpus/${transfer.row.id}` }).then((res: any) => {
- setQuestion(res.question)
- setIntentValue([res.intention.type_id, res.intention.id])
- setQuestionCorpus(res.question_config || '')
- setSimilarityList(res.similarity_questions)
- })
- }
- }, [])
- const [similarityQuestion, setSimilarityQuestion] = useState<string>('')
- const [similarityFilter, setSimilarityFilter] = useState<string>('')
- const refreshSimilarity = async () => {
- const res = await fetchCorpusQuestion({
- url: `/intentions/corpus/${transfer.row.id}/similarity_questions`,
- params: {
- page: 1,
- limit: 99999,
- },
- })
- setSimilarityList(res)
- }
- const handleAddSimilarity = async () => {
- if (!similarityQuestion)
- return
- if (similarityList.some((v: any) => v.question === similarityQuestion)) {
- notify({ type: 'warning', message: '请勿新增重复数据!' })
- return
- }
- const { id }: any = await addCorpusQuestion({
- url: `/intentions/corpus/${transfer.row.id}/similarity_questions`,
- body: {
- question: similarityQuestion,
- },
- })
- if (id) {
- await refreshSimilarity()
- setSimilarityQuestion('')
- }
- }
- const [similaritySelectMap, setSimilaritySelectMap] = useState<any>(new Map())
- const addSimilaritySelectMap = (key: any, value: any) => {
- setSimilaritySelectMap((prevMap: any) => {
- const newMap = new Map(prevMap)
- newMap.set(key, value)
- return newMap
- })
- }
- const delSimilaritySelectMap = (key: any) => {
- setSimilaritySelectMap((prevMap: any) => {
- const newMap = new Map(prevMap)
- newMap.delete(key)
- return newMap
- })
- }
- const [similarityRow, setSimilarityRow] = useState<any>({})
- const [showSimilarityEdit, setShowSimilarityEdit] = useState(false)
- const [editSimilarity, setEditSimilarity] = useState<string>('')
- const [showSimilarityCorpus, setShowSimilarityCorpus] = useState(false)
- const [editSimilarityCorpus, setEditSimilarityCorpus] = useState('')
- const handleSaveSimilarity = async () => {
- if (similarityList.some((v: any) => v.name === editSimilarity)) {
- notify({ type: 'warning', message: '请勿新增重复数据!' })
- return
- }
- const params: any = {
- corpus_id: similarityRow.corpus_id,
- }
- if (showSimilarityEdit) {
- params.question = editSimilarity
- }
- else if (showSimilarityCorpus) {
- params.question = similarityRow.question
- params.question_config = editSimilarityCorpus
- }
- const { id }: any = await editCorpusQuestion({
- url: `/intentions/similarity_questions/${similarityRow.id}`,
- body: params,
- })
- if (id) {
- await refreshSimilarity()
- setShowSimilarityEdit(false)
- setShowSimilarityCorpus(false)
- }
- }
- const [showConfirmDelete, setShowConfirmDelete] = useState(false)
- const [delBatch, setDelBatch] = useState(false)
- const handleDelSimilarity = async () => {
- try {
- await delBatchCorpusQuestion({
- url: '/intentions/similarity_questions/batch',
- body: {
- method: 'delete',
- data: delBatch ? Array.from(similaritySelectMap.keys()) : [similarityRow.id],
- },
- })
- setShowConfirmDelete(false)
- setSimilaritySelectMap(new Map())
- refreshSimilarity()
- }
- catch (e) { }
- }
- const [showCorpus, setShowCorpus] = useState(false)
- const [editCorpus, setEditCorpus] = useState('')
- return (
- <div>
- <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[800px] max-w-[800px]">
- <div className='mb-2 flex justify-between'>
- <div className='text-xl font-semibold text-text-primary'>{transfer.mode === 'add' ? '新增' : '编辑'}语料</div>
- <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
- </div>
- <div className="shrink-0 text-gray-500">
- <div className="flex flex-col gap-2">
- <div className="flex w-full items-center">
- <div className="w-[80px]">标准问题</div>
- <div className="flex-1">
- <Input
- showClearIcon
- value={question}
- onChange={e => setQuestion(e.target.value)}
- onClear={() => setQuestion('')}
- />
- </div>
- {
- transfer.mode === 'edit' && (
- <Button variant='ghost-accent' size='small' className={cn('shrink-0')} onClick={() => {
- setEditCorpus(questionCorpus)
- setShowCorpus(true)
- }}>
- 语料配置
- </Button>
- )
- }
- </div>
- <div className="flex w-full items-center">
- <div className="w-[80px]">意图名称</div>
- <div className="flex flex-1">
- <AntdCascader
- value={intentValue}
- className="h-[32px] w-full"
- options={intentCascader}
- onChange={(val: any) => {
- setIntentValue(val)
- }}
- placeholder="请选择"
- fieldNames={{ label: 'name', value: 'id' }}
- showSearch={true}
- />
- </div>
- </div>
- {
- transfer.mode === 'edit' && (
- <div className="flex w-full items-center">
- <div className="w-[80px]">相似问题</div>
- <div className="flex-1">
- <Input
- showClearIcon
- value={similarityQuestion}
- onChange={e => setSimilarityQuestion(e.target.value)}
- onClear={() => setSimilarityQuestion('')}
- placeholder='输入后Enter以添加'
- onEnter={handleAddSimilarity}
- />
- </div>
- </div>
- )
- }
- </div>
- {
- transfer.mode === 'edit' && (
- <div className="mt-3 flex flex-col">
- <div className="flex h-10 w-full items-center gap-2 border-2 border-[#F6F8FC] bg-[#F6F8FC] px-2">
- <div className='flex items-center' onClick={e => e.stopPropagation()}>
- <Checkbox
- className='mr-2 shrink-0'
- checked={similarityList.every((v: any) => similaritySelectMap.has(v.id))}
- onCheck={() => {
- similarityList.every((v: any) => similaritySelectMap.has(v.id))
- ? setSimilaritySelectMap(new Map())
- : similarityList.forEach((v: any) => addSimilaritySelectMap(v.id, v))
- }}
- disabled={similarityList.length === 0}
- />
- 全选
- </div>
- <div className="ml-auto w-[200px]">
- <Input
- showClearIcon
- value={similarityFilter}
- onChange={e => setSimilarityFilter(e.target.value)}
- onClear={() => setSimilarityFilter('')}
- placeholder='请输入相似问题名称进行过滤'
- />
- </div>
- <Button variant='primary' className={cn('shrink-0')} onClick={() => {
- setDelBatch(true)
- setShowConfirmDelete(true)
- }}>
- 批量删除
- </Button>
- </div>
- <div className="flex h-[300px] flex-col gap-2 overflow-y-auto border-2 border-solid border-[#F6F8FC] p-2">
- {
- similarityList.filter((v: any) => !similarityFilter || v.question.includes(similarityFilter)).map((item: any) => (
- <div key={item.id} className="flex items-center">
- <Checkbox
- className='mr-2 shrink-0'
- checked={similaritySelectMap.has(item.id)}
- onCheck={() => {
- similaritySelectMap.has(item.id)
- ? delSimilaritySelectMap(item.id)
- : addSimilaritySelectMap(item.id, item)
- }}
- disabled={similarityList.length === 0}
- />
- <div className="flex-1">
- {item.question}
- </div>
- <Button variant='ghost-accent' size='small' className={cn('shrink-0')} onClick={() => {
- setSimilarityRow(item)
- setEditSimilarityCorpus(item.question_config || '')
- setShowSimilarityCorpus(true)
- }}>
- 语料配置
- </Button>
- <Button variant='ghost-accent' size='small' className={cn('shrink-0')}
- onClick={() => {
- setSimilarityRow(item)
- setEditSimilarity(item.question)
- setShowSimilarityEdit(true)
- }}>
- 编辑
- </Button>
- <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')}
- onClick={() => {
- setSimilarityRow(item)
- setShowConfirmDelete(true)
- }}>
- 刪除
- </Button>
- </div>
- ))
- }
- </div>
- <div className="flex border-2 border-t-0 border-solid border-[#F6F8FC] p-2 text-xs">
- <div>共{similarityList.length}条</div>
- <div className="ml-4">已选择{similaritySelectMap.size}条</div>
- </div>
- </div>
- )
- }
- <Button
- tabIndex={0}
- className='mt-4 w-full'
- onClick={handleSave}
- disabled={!question.length || !intentValue.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </Modal>
- {
- showSimilarityEdit && (
- <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[400px]">
- <div className='mb-2 flex justify-between'>
- <div className='text-xl font-semibold text-text-primary'>编辑相似问题</div>
- <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowSimilarityEdit(false)} />
- </div>
- <div>
- <div className={cn('flex flex-wrap items-center justify-between py-4')}>
- <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
- 相似问题
- </div>
- <Input
- value={editSimilarity}
- onChange={e => setEditSimilarity(e.target.value)}
- className='h-9'
- placeholder='请输入相似问题'
- />
- </div>
- <Button
- tabIndex={0}
- className='w-full'
- onClick={handleSaveSimilarity}
- disabled={!editSimilarity.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </Modal>
- )
- }
- {showConfirmDelete && (
- <Confirm
- title="删除确认"
- content={`请确认是否删除${delBatch ? `${similaritySelectMap.size}条相似问题` : similarityRow.question}?`}
- isShow={showConfirmDelete}
- onConfirm={handleDelSimilarity}
- onCancel={() => setShowConfirmDelete(false)}
- />
- )}
- {
- showCorpus && (
- <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] max-w-[800px]">
- <div className='mb-2 flex justify-between'>
- <div className='text-xl font-semibold text-text-primary'>训练语料配置</div>
- <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowCorpus(false)} />
- </div>
- <div>
- <div className={cn('flex flex-wrap items-center justify-between py-4')}>
- <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
- 当前标注问题:{question}
- </div>
- <Textarea
- value={editCorpus}
- onChange={e => setEditCorpus(e.target.value)}
- className='resize-none'
- placeholder='请输入语料配置'
- rows={30}
- />
- </div>
- <div className="flex gap-2">
- <Button
- className='w-full'
- onClick={() => setEditCorpus(questionCorpus)}
- variant='warning'
- >
- 重置
- </Button>
- <Button
- className='w-full'
- onClick={() => {
- setQuestionCorpus(editCorpus)
- setShowCorpus(false)
- }}
- disabled={!editCorpus.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </div>
- </Modal>
- )
- }
- {
- showSimilarityCorpus && (
- <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] max-w-[800px]">
- <div className='mb-2 flex justify-between'>
- <div className='text-xl font-semibold text-text-primary'>训练语料配置</div>
- <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowSimilarityCorpus(false)} />
- </div>
- <div>
- <div className={cn('flex flex-wrap items-center justify-between py-4')}>
- <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
- 当前标注问题:{similarityRow.question}
- </div>
- <Textarea
- value={editSimilarityCorpus}
- onChange={e => setEditSimilarityCorpus(e.target.value)}
- className='resize-none'
- placeholder='请输入语料配置'
- rows={30}
- />
- </div>
- <div className="flex gap-2">
- <Button
- className='w-full'
- onClick={() => setEditSimilarityCorpus(similarityRow.question_config || '')}
- variant='warning'
- >
- 重置
- </Button>
- <Button
- className='w-full'
- onClick={handleSaveSimilarity}
- disabled={!editSimilarityCorpus.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </div>
- </Modal>
- )
- }
- </div>
- )
- }
- export default DetailModal
|