detail-modal.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. 'use client'
  2. import React, { useCallback, useState } from 'react'
  3. import { RiCloseLine } from '@remixicon/react'
  4. import Modal from '@/app/components/base/modal'
  5. import Button from '@/app/components/base/button'
  6. import { delCorpusQuestion, fetchIntentName, fetchIntentType } from '@/service/common'
  7. import 'react-multi-email/dist/style.css'
  8. import Input from '@/app/components/base/input'
  9. import { SimpleSelect } from '@/app/components/base/select'
  10. import useSWR from 'swr'
  11. import { v4 as uuid4 } from 'uuid'
  12. import Checkbox from '@/app/components/base/checkbox'
  13. import cn from '@/utils/classnames'
  14. import { useContext } from 'use-context-selector'
  15. import { ToastContext } from '@/app/components/base/toast'
  16. import Confirm from '@/app/components/base/confirm'
  17. import Textarea from '@/app/components/base/textarea'
  18. const DetailModal = ({
  19. transfer,
  20. onCancel,
  21. onSend,
  22. }: any) => {
  23. const { notify } = useContext(ToastContext)
  24. const [questionRelation, setQuestionRelation] = useState<string>('')
  25. const [questionFilter, setQuestionFilter] = useState<string>('') // the input value
  26. const [question, setQuestion] = useState<string>(transfer.row?.question || '') // the input value
  27. const [intentType, setIntentType] = useState<string>(transfer.row?.intentType || '') // the input value
  28. const { data: dataOptionsIntentType }: any = useSWR(
  29. {
  30. url: '/xxx',
  31. params: {
  32. page: 1,
  33. limit: 1000,
  34. },
  35. },
  36. fetchIntentType,
  37. )
  38. const optionsIntentType: any = dataOptionsIntentType?.data.map((v: any) => ({ name: v.name, value: v.id })) || []
  39. const [intentName, setIntentName] = useState<string>(transfer.row?.intentName || '') // the input value
  40. const { data: dataOptionsIntentName }: any = useSWR(
  41. {
  42. url: '/xxx',
  43. params: {
  44. page: 1,
  45. limit: 1000,
  46. intentType,
  47. },
  48. },
  49. fetchIntentName,
  50. )
  51. const optionsIntentName: any = dataOptionsIntentName?.data.map((v: any) => ({ name: v.name, value: v.id })) || []
  52. const [questionList, setQuestionList] = useState<any>([{ id: uuid4(), name: '啊啊啊啊啊啊啊啊啊啊' }, { id: uuid4(), name: '啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊' }])
  53. const handleAddQuestion = () => {
  54. if (!questionRelation)
  55. return
  56. if (questionList.some((v: any) => v.name === questionRelation)) {
  57. notify({ type: 'warning', message: '请勿新增重复数据!' })
  58. return
  59. }
  60. setQuestionList([...questionList, { id: uuid4(), name: questionRelation }])
  61. setQuestionRelation('')
  62. }
  63. const [questionSelectMap, setQuestionSelectMap] = useState<any>(new Map())
  64. const addQuestionSelectMap = (key: any, value: any) => {
  65. setQuestionSelectMap((prevMap: any) => {
  66. const newMap = new Map(prevMap)
  67. newMap.set(key, value)
  68. return newMap
  69. })
  70. }
  71. const delQuestionSelectMap = (key: any) => {
  72. setQuestionSelectMap((prevMap: any) => {
  73. const newMap = new Map(prevMap)
  74. newMap.delete(key)
  75. return newMap
  76. })
  77. }
  78. const [showConfirmDelete, setShowConfirmDelete] = useState(false)
  79. const [row, setRow] = useState<any>({})
  80. const handleDelQuestion = async () => {
  81. try {
  82. await delCorpusQuestion({
  83. url: `/xxx/${row.id}`,
  84. body: {},
  85. })
  86. setShowConfirmDelete(false)
  87. // mutate()
  88. }
  89. catch (e) { }
  90. }
  91. const [showQuestionEdit, setShowQuestionEdit] = useState(false)
  92. const [editQuestion, setEditQuestion] = useState<string>('')
  93. const [showCorpus, setShowCorpus] = useState(false)
  94. const [corpusRow, setCorpusRow] = useState<any>({})
  95. const [corpusInput, setCorpusInput] = useState<string>('')
  96. const handleSave = useCallback(async () => {
  97. // try {
  98. // let res
  99. // if (transfer.mode === 'add') {
  100. // res = await addCorpus({
  101. // url: '/xxx',
  102. // body: { name, type: 'knowledge_category' },
  103. // })
  104. // }
  105. // else {
  106. // res = await editCorpus({
  107. // url: '/xxx',
  108. // body: { name },
  109. // })
  110. // }
  111. // const { id }: any = res
  112. // if (id) {
  113. // onCancel()
  114. // onSend()
  115. // }
  116. // }
  117. // catch (e) { }
  118. }, [name, onCancel, onSend, transfer])
  119. const handleSaveQuestion = useCallback(async () => {
  120. // try {
  121. // let res
  122. // if (transfer.mode === 'add') {
  123. // res = await addCorpus({
  124. // url: '/xxx',
  125. // body: { name, type: 'knowledge_category' },
  126. // })
  127. // }
  128. // else {
  129. // res = await editCorpus({
  130. // url: '/xxx',
  131. // body: { name },
  132. // })
  133. // }
  134. // const { id }: any = res
  135. // if (id) {
  136. // onCancel()
  137. // onSend()
  138. // }
  139. // }
  140. // catch (e) { }
  141. }, [name, onCancel, onSend, transfer])
  142. const handleSaveCorpus = useCallback(async () => {
  143. // try {
  144. // let res
  145. // if (transfer.mode === 'add') {
  146. // res = await addCorpus({
  147. // url: '/xxx',
  148. // body: { name, type: 'knowledge_category' },
  149. // })
  150. // }
  151. // else {
  152. // res = await editCorpus({
  153. // url: '/xxx',
  154. // body: { name },
  155. // })
  156. // }
  157. // const { id }: any = res
  158. // if (id) {
  159. // onCancel()
  160. // onSend()
  161. // }
  162. // }
  163. // catch (e) { }
  164. }, [name, onCancel, onSend, transfer])
  165. return (
  166. <div>
  167. <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[800px] max-w-[800px]">
  168. <div className='mb-2 flex justify-between'>
  169. <div className='text-xl font-semibold text-text-primary'>{transfer.mode === 'add' ? '新增' : '编辑'}语料</div>
  170. <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
  171. </div>
  172. <div className="shrink-0 text-gray-500">
  173. <div className="flex flex-col gap-2">
  174. <div className="flex w-full items-center">
  175. <div className="w-[80px]">标准问题</div>
  176. <div className="flex-1">
  177. <Input
  178. showClearIcon
  179. value={question}
  180. onChange={e => setQuestion(e.target.value)}
  181. onClear={() => setQuestion('')}
  182. />
  183. </div>
  184. {
  185. transfer.mode === 'edit' && (
  186. <Button variant='ghost-accent' size='small' className={cn('shrink-0')} onClick={() => {
  187. setRow({
  188. type: 'main',
  189. name: question,
  190. })
  191. setCorpusInput(question)
  192. setShowCorpus(true)
  193. }}>
  194. 语料配置
  195. </Button>
  196. )
  197. }
  198. </div>
  199. <div className="flex w-full items-center">
  200. <div className="w-[80px]">意图名称</div>
  201. <div className="flex flex-1">
  202. <SimpleSelect
  203. className="h-[32px] w-[200px]"
  204. defaultValue={intentType}
  205. onSelect={(i: any) => {
  206. setIntentType(i.value)
  207. setIntentName('')
  208. }}
  209. items={optionsIntentType}
  210. allowSearch={false}
  211. placeholder="请选择意图类型"
  212. />
  213. <SimpleSelect
  214. className="h-[32px] w-[200px]"
  215. defaultValue={intentName}
  216. onSelect={(i: any) => {
  217. setIntentName(i.value)
  218. }}
  219. items={optionsIntentName}
  220. allowSearch={false}
  221. placeholder="请选择意图名称"
  222. disabled={!intentType}
  223. />
  224. </div>
  225. </div>
  226. {
  227. transfer.mode === 'edit' && (
  228. <div className="flex w-full items-center">
  229. <div className="w-[80px]">相似问题</div>
  230. <div className="flex-1">
  231. <Input
  232. showClearIcon
  233. value={questionRelation}
  234. onChange={e => setQuestionRelation(e.target.value)}
  235. onClear={() => setQuestionRelation('')}
  236. placeholder='输入后Enter以添加'
  237. onEnter={handleAddQuestion}
  238. />
  239. </div>
  240. </div>
  241. )
  242. }
  243. </div>
  244. {
  245. transfer.mode === 'edit' && (
  246. <div className="mt-3 flex flex-col">
  247. <div className="flex h-10 w-full items-center gap-2 border-2 border-[#F6F8FC] bg-[#F6F8FC] px-2">
  248. <div className='flex items-center' onClick={e => e.stopPropagation()}>
  249. <Checkbox
  250. className='mr-2 shrink-0'
  251. checked={questionList.every((v: any) => questionSelectMap.has(v.id))}
  252. onCheck={() => {
  253. questionList.every((v: any) => questionSelectMap.has(v.id))
  254. ? setQuestionSelectMap(new Map())
  255. : questionList.forEach((v: any) => addQuestionSelectMap(v.id, v))
  256. }}
  257. disabled={questionList.length === 0}
  258. />
  259. 全选
  260. </div>
  261. <div className="ml-auto w-[200px]">
  262. <Input
  263. showClearIcon
  264. value={questionFilter}
  265. onChange={e => setQuestionFilter(e.target.value)}
  266. onClear={() => setQuestionFilter('')}
  267. placeholder='请输入相似问题名称进行过滤'
  268. />
  269. </div>
  270. <Button variant='primary' className={cn('shrink-0')}>
  271. 批量删除
  272. </Button>
  273. </div>
  274. <div className="flex h-[300px] flex-col gap-2 overflow-y-auto border-2 border-solid border-[#F6F8FC] p-2">
  275. {
  276. questionList.filter((v: any) => !questionFilter || v.name.includes(questionFilter)).map((item: any) => (
  277. <div key={item.id} className="flex items-center">
  278. <Checkbox
  279. className='mr-2 shrink-0'
  280. checked={questionSelectMap.has(item.id)}
  281. onCheck={() => {
  282. questionSelectMap.has(item.id)
  283. ? delQuestionSelectMap(item.id)
  284. : addQuestionSelectMap(item.id, item)
  285. }}
  286. disabled={questionList.length === 0}
  287. />
  288. <div className="flex-1">
  289. {item.name}
  290. </div>
  291. <Button variant='ghost-accent' size='small' className={cn('shrink-0')} onClick={() => {
  292. setRow(item)
  293. setCorpusInput(item.name)
  294. setShowCorpus(true)
  295. }}>
  296. 语料配置
  297. </Button>
  298. <Button variant='ghost-accent' size='small' className={cn('shrink-0')}
  299. onClick={() => {
  300. setRow(item)
  301. setEditQuestion(item.name)
  302. setShowQuestionEdit(true)
  303. }}>
  304. 编辑
  305. </Button>
  306. <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')}
  307. onClick={() => {
  308. setRow(item)
  309. setShowConfirmDelete(true)
  310. }}>
  311. 刪除
  312. </Button>
  313. </div>
  314. ))
  315. }
  316. </div>
  317. <div className="flex border-2 border-t-0 border-solid border-[#F6F8FC] p-2 text-xs">
  318. <div>共{questionList.length}条</div>
  319. <div className="ml-4">已选择{questionSelectMap.size}条</div>
  320. </div>
  321. </div>
  322. )
  323. }
  324. <Button
  325. tabIndex={0}
  326. className='mt-4 w-full'
  327. onClick={handleSave}
  328. disabled={!question.length || !intentType.length || !intentName.length}
  329. variant='primary'
  330. >
  331. 保存
  332. </Button>
  333. </div>
  334. </Modal>
  335. {
  336. showQuestionEdit && (
  337. <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[400px]">
  338. <div className='mb-2 flex justify-between'>
  339. <div className='text-xl font-semibold text-text-primary'>编辑相似问题</div>
  340. <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowQuestionEdit(false)} />
  341. </div>
  342. <div>
  343. <div className={cn('flex flex-wrap items-center justify-between py-4')}>
  344. <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
  345. 相似问题
  346. </div>
  347. <Input
  348. value={editQuestion}
  349. onChange={e => setEditQuestion(e.target.value)}
  350. className='h-9'
  351. placeholder='请输入相似问题'
  352. />
  353. </div>
  354. <Button
  355. tabIndex={0}
  356. className='w-full'
  357. onClick={handleSaveQuestion}
  358. disabled={!editQuestion.length}
  359. variant='primary'
  360. >
  361. 保存
  362. </Button>
  363. </div>
  364. </Modal>
  365. )
  366. }
  367. {
  368. showCorpus && (
  369. <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] max-w-[800px]">
  370. <div className='mb-2 flex justify-between'>
  371. <div className='text-xl font-semibold text-text-primary'>训练语料配置</div>
  372. <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowCorpus(false)} />
  373. </div>
  374. <div>
  375. <div className={cn('flex flex-wrap items-center justify-between py-4')}>
  376. <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
  377. 当前标注问题:{row.name}
  378. </div>
  379. <Textarea
  380. value={corpusInput}
  381. onChange={e => setCorpusInput(e.target.value)}
  382. className='resize-none'
  383. placeholder='请输入语料配置'
  384. rows={30}
  385. />
  386. </div>
  387. <div className="flex gap-2">
  388. <Button
  389. className='w-full'
  390. onClick={() => setCorpusInput(row.name)}
  391. variant='warning'
  392. >
  393. 重置
  394. </Button>
  395. <Button
  396. className='w-full'
  397. onClick={handleSaveCorpus}
  398. disabled={!corpusInput.length}
  399. variant='primary'
  400. >
  401. 保存
  402. </Button>
  403. </div>
  404. </div>
  405. </Modal>
  406. )
  407. }
  408. {showConfirmDelete && (
  409. <Confirm
  410. title="删除确认"
  411. content={`请确认是否删除${row.name}?`}
  412. isShow={showConfirmDelete}
  413. onConfirm={handleDelQuestion}
  414. onCancel={() => setShowConfirmDelete(false)}
  415. />
  416. )}
  417. </div>
  418. )
  419. }
  420. export default DetailModal