123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- '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 {
- addIntent, addIntentKeyword, delBatchIntentKeyword, editIntent, editIntentKeyword, fetchIntentKeyword,
- fetchIntentType, getIntent,
- } from '@/service/common'
- import 'react-multi-email/dist/style.css'
- import Input from '@/app/components/base/input'
- import { SimpleSelect } from '@/app/components/base/select'
- import useSWR from 'swr'
- 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 { Textarea } from '@/app/components/base/textarea'
- const DetailModal = ({
- transfer,
- onCancel,
- onSend,
- onRefresh,
- }: any) => {
- const { notify } = useContext(ToastContext)
- const [intentName, setIntentName] = useState<string>(transfer.row?.intentName || '')
- const [intentType, setIntentType] = useState<string>(transfer.row?.intentType || '')
- const [corpusList, setCorpusList] = useState<any>([])
- const [corpusFilter, setCorpusFilter] = useState<string>('')
- const [keywordsList, setKeywordsList] = useState<any>([])
- const { data: dataOptionsIntentType }: any = useSWR(
- {
- url: '/intentions/types',
- params: {
- page: 1,
- limit: 99999,
- },
- },
- fetchIntentType,
- )
- const optionsIntentType: any = dataOptionsIntentType?.data.map((v: any) => ({ name: v.name, value: v.id })) || []
- useEffect(() => {
- if (transfer.row?.id) {
- getIntent({ url: `/intentions/${transfer.row.id}` }).then((res: any) => {
- setIntentType(res.type.id)
- setIntentName(res.name)
- setCorpusList(res.corpus)
- setKeywordsList(res.keywords)
- })
- }
- }, [])
- const [keyword, setKeyword] = useState<string>('')
- const [keywordFilter, setKeywordFilter] = useState<string>('')
- const refreshKeywords = async () => {
- const res = await fetchIntentKeyword({
- url: `/intentions/${transfer.row.id}/keywords`,
- params: {
- page: 1,
- limit: 99999,
- },
- })
- setKeywordsList(res)
- }
- const handleAddKeyword = async () => {
- if (!keyword)
- return
- if (keywordsList.some((v: any) => v.name === keyword)) {
- notify({ type: 'warning', message: '请勿新增重复数据!' })
- return
- }
- const { id }: any = await addIntentKeyword({
- url: `/intentions/${transfer.row.id}/keywords`,
- body: {
- name: keyword,
- },
- })
- if (id) {
- await refreshKeywords()
- setKeyword('')
- }
- }
- const [keywordSelectMap, setKeywordsSelectMap] = useState<any>(new Map())
- const addKeywordsSelectMap = (key: any, value: any) => {
- setKeywordsSelectMap((prevMap: any) => {
- const newMap = new Map(prevMap)
- newMap.set(key, value)
- return newMap
- })
- }
- const delKeywordsSelectMap = (key: any) => {
- setKeywordsSelectMap((prevMap: any) => {
- const newMap = new Map(prevMap)
- newMap.delete(key)
- return newMap
- })
- }
- const [keywordRow, setKeywordRow] = useState<any>({})
- const [showKeywordEdit, setShowKeywordEdit] = useState(false)
- const [editKeyword, setEditKeyword] = useState<string>('')
- const handleSaveKeyword = async () => {
- if (keywordsList.some((v: any) => v.name === editKeyword)) {
- notify({ type: 'warning', message: '请勿新增重复数据!' })
- return
- }
- const { id }: any = await editIntentKeyword({
- url: `/intentions/keywords/${keywordRow.id}`,
- body: {
- name: editKeyword,
- intention_id: keywordRow.intention_id,
- },
- })
- if (id) {
- await refreshKeywords()
- setShowKeywordEdit(false)
- }
- }
- const [showConfirmDelete, setShowConfirmDelete] = useState(false)
- const [delBatch, setDelBatch] = useState(false)
- const handleDelKeyword = async () => {
- try {
- await delBatchIntentKeyword({
- url: '/intentions/keywords/batch',
- body: {
- method: 'delete',
- delete_data: delBatch ? Array.from(keywordSelectMap.keys()) : [keywordRow.id],
- },
- })
- setShowConfirmDelete(false)
- setKeywordsSelectMap(new Map())
- refreshKeywords()
- }
- catch (e) { }
- }
- const handleSave = async () => {
- try {
- let res
- if (transfer.mode === 'add') {
- res = await addIntent({
- url: '/intentions',
- body: { type_id: intentType, name: intentName },
- })
- }
- else {
- res = await editIntent({
- url: `/intentions/${transfer.row.id}`,
- body: { type_id: intentType, name: intentName },
- })
- }
- const { id }: any = res
- if (id) {
- if (transfer.mode === 'add')
- onRefresh(id)
- else
- onSend()
- }
- }
- catch (e) { }
- }
- const [similarityRow, setSimilarityRow] = useState<any>({})
- const [showSimilarityCorpus, setShowSimilarityCorpus] = useState(false)
- 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 flex-1">
- <SimpleSelect
- className="h-[32px] w-[200px]"
- defaultValue={intentType}
- onSelect={(i: any) => {
- setIntentType(i.value)
- }}
- items={optionsIntentType}
- allowSearch={false}
- placeholder="请选择意图类型"
- />
- </div>
- </div>
- <div className="flex w-full items-center">
- <div className="w-[80px]">意图名称</div>
- <div className="flex-1">
- <Input
- showClearIcon
- value={intentName}
- onChange={e => setIntentName(e.target.value)}
- onClear={() => setIntentName('')}
- />
- </div>
- </div>
- {
- transfer.mode === 'edit' && (
- <div className="flex w-full items-center">
- <div className="w-[80px]">关键词</div>
- <div className="flex-1">
- <Input
- showClearIcon
- value={keyword}
- onChange={e => setKeyword(e.target.value)}
- onClear={() => setKeyword('')}
- placeholder='输入后Enter以添加'
- onEnter={handleAddKeyword}
- />
- </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={keywordsList.every((v: any) => keywordSelectMap.has(v.id))}
- onCheck={() => {
- keywordsList.every((v: any) => keywordSelectMap.has(v.id))
- ? setKeywordsSelectMap(new Map())
- : keywordsList.forEach((v: any) => addKeywordsSelectMap(v.id, v))
- }}
- disabled={keywordsList.length === 0}
- />
- 全选
- </div>
- <div className="ml-auto w-[200px]">
- <Input
- showClearIcon
- value={keywordFilter}
- onChange={e => setKeywordFilter(e.target.value)}
- onClear={() => setKeywordFilter('')}
- placeholder='请输入关键词名称进行过滤'
- />
- </div>
- <Button variant='primary' className={cn('shrink-0')} onClick={() => {
- setDelBatch(true)
- setShowConfirmDelete(true)
- }}>
- 批量删除
- </Button>
- </div>
- <div className="flex h-[150px] flex-col gap-2 overflow-y-auto border-2 border-solid border-[#F6F8FC] p-2">
- {
- keywordsList.filter((v: any) => !keywordFilter || v.name.includes(keywordFilter)).map((item: any) => (
- <div key={item.id} className="flex items-center">
- <Checkbox
- className='mr-2 shrink-0'
- checked={keywordSelectMap.has(item.id)}
- onCheck={() => {
- keywordSelectMap.has(item.id)
- ? delKeywordsSelectMap(item.id)
- : addKeywordsSelectMap(item.id, item)
- }}
- disabled={keywordsList.length === 0}
- />
- <div className="flex-1">
- {item.name}
- </div>
- <Button variant='ghost-accent' size='small' className={cn('shrink-0')}
- onClick={() => {
- setKeywordRow(item)
- setEditKeyword(item.name)
- setShowKeywordEdit(true)
- }}>
- 编辑
- </Button>
- <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')}
- onClick={() => {
- setKeywordRow(item)
- setShowConfirmDelete(true)
- }}>
- 刪除
- </Button>
- </div>
- ))
- }
- </div>
- <div className="flex border-2 border-t-0 border-solid border-[#F6F8FC] p-2 text-xs">
- <div>共{keywordsList.length}条</div>
- <div className="ml-4">已选择{keywordSelectMap.size}条</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>语料列表</div>
- <div className="ml-auto w-[200px]">
- <Input
- showClearIcon
- value={corpusFilter}
- onChange={e => setCorpusFilter(e.target.value)}
- onClear={() => setCorpusFilter('')}
- placeholder='请输入语料名称进行过滤'
- />
- </div>
- </div>
- <div className="flex h-[150px] flex-col gap-2 overflow-y-auto border-2 border-solid border-[#F6F8FC] p-2">
- {
- corpusList.filter((v: any) => !corpusFilter || v.question.includes(corpusFilter)).map((item: any) => (
- <div key={item.id} className="flex items-center">
- <div className="flex-1">
- {item.question}
- </div>
- <Button variant='ghost-accent' size='small' className={cn('shrink-0')} onClick={() => {
- setSimilarityRow(item)
- setShowSimilarityCorpus(true)
- }}>
- 语料配置
- </Button>
- </div>
- ))
- }
- </div>
- <div className="flex border-2 border-t-0 border-solid border-[#F6F8FC] p-2 text-xs">
- <div>共{corpusList.length}条</div>
- </div>
- </div>
- )
- }
- <Button
- tabIndex={0}
- className='mt-2 w-full'
- onClick={handleSave}
- disabled={!intentType.length || !intentName.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </Modal>
- {
- showKeywordEdit && (
- <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={() => setShowKeywordEdit(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={editKeyword}
- onChange={e => setEditKeyword(e.target.value)}
- className='h-9'
- placeholder='请输入关键词'
- />
- </div>
- <Button
- tabIndex={0}
- className='w-full'
- onClick={handleSaveKeyword}
- disabled={!editKeyword.length}
- variant='primary'
- >
- 保存
- </Button>
- </div>
- </Modal>
- )
- }
- {showConfirmDelete && (
- <Confirm
- title="删除确认"
- content={`请确认是否删除${delBatch ? `${keywordSelectMap.size}条关键词` : keywordRow.name}?`}
- isShow={showConfirmDelete}
- onConfirm={handleDelKeyword}
- onCancel={() => setShowConfirmDelete(false)}
- />
- )}
- {
- 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={similarityRow.question_config || ''}
- className='resize-none'
- rows={30}
- disabled={true}
- />
- </div>
- </div>
- </Modal>
- )
- }
- </div>
- )
- }
- export default DetailModal
|