This commit is contained in:
zhangkai
2024-09-03 11:12:51 +08:00
parent e12f955105
commit 94be64311c
43 changed files with 1255 additions and 200 deletions

View File

@@ -0,0 +1,110 @@
import { LabelIcon } from "@/components/bs-icons/label";
import LabelSelect from "../selectComponent/LabelSelect";
import { UPDATETYPE } from "../selectComponent/LabelSelect";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { BookmarkFilledIcon } from "@radix-ui/react-icons";
import tianjiabiaoqian from "../../../assets/npc/tianjiabiaoqian.png";
export default function LabelShow({ show, isOperator, labels, all, resource, reload, AllLabelsApi }) {
const { t } = useTranslation()
const [freshData, setFreshData] = useState(labels)
const [allData, setAllData] = useState(all)
const [isShow, setIsShow] = useState(show)
const handleUpdate = (obj: { type: string, data: any }) => {
console.log(UPDATETYPE,obj)
switch (obj.type) {
case UPDATETYPE.DELETELINK: {
reload();
AllLabelsApi();
setFreshData(pre => pre.filter(l => l.value !== obj.data.value))
break
}
case UPDATETYPE.CREATELINK: {
reload();
AllLabelsApi();
setFreshData(pre => [obj.data, ...pre])
break
}
case UPDATETYPE.UPDATENAME: {
reload();
AllLabelsApi();
setFreshData(pre => pre.map(d => d.value === obj.data.value ? { ...d, label: obj.data.label } : d))
break
}
case UPDATETYPE.CREATELABEL: {
// 什么也不用做
break
}
case UPDATETYPE.DELETELABEL: {
reload();
AllLabelsApi();
setFreshData(pre => pre.filter(d => d.value !== obj.data.value))
setAllData(pre => pre.filter(a => a.value !== obj.data.value))
break
}
default: console.log('error>>事件类型错误!!!')
}
}
useEffect(() => {
setIsShow(freshData.length > 0)
}, [freshData])
return (
<div className="w-full">
{isShow ? (
isOperator ? (
<LabelSelect onUpdate={handleUpdate} labels={labels} resource={resource} all={allData}>
<div onClick={(e) => e.stopPropagation()} className="mb-[10px] max-w-[100%] flex place-items-center rounded-sm p-1 border border-transparent group-hover:bg-search-input group-hover:border-input">
{/* <BookmarkFilledIcon className="mr-2 text-muted-foreground" />
<div className="text-sm text-muted-foreground max-w-[250px] truncate">
{freshData.map((l, index) => <span>{l.label}{index !== freshData.length - 1 && ''}</span>)}
</div> */}
<div className="biaoqian">
<img src={tianjiabiaoqian} className="w-[12px] mr-[7px]" alt="" />
<div>
{freshData.map((l, index) => <div>{l.label}</div>)}
{/* <div>知识库</div> */}
{/* <div>法律</div> */}
</div>
</div>
</div>
</LabelSelect>
) : (
<div className="mb-[10px] flex place-items-center max-w-[100%] rounded-sm p-1">
{/* <BookmarkFilledIcon className="mr-2 text-muted-foreground" />
<div className="text-sm text-muted-foreground max-w-[250px] truncate">
{freshData.map((l, index) => <span>{l.label}{index !== freshData.length - 1 && ''}</span>)}
</div> */}
<div className="biaoqian">
{/* <img src={tianjiabiaoqian} className="w-[12px]" alt="" /> */}
<div>
{freshData.map((l, index) => <div>{l.label}</div>)}
{/* <div>知识库</div>
<div>法律</div> */}
</div>
</div>
</div>
)
) : (
isOperator ? (
<LabelSelect onUpdate={handleUpdate} labels={labels} resource={resource} all={allData}>
<div onClick={(e) => e.stopPropagation()} className="">
{/* <BookmarkFilledIcon className="mr-2 text-muted-foreground" />
<div className="text-sm text-muted-foreground">
<span>{t('tag.addLabel')}</span>
</div> */}
<div className="biaoqian">
<img src={tianjiabiaoqian} className="w-[12px]" alt="" />
</div>
</div>
</LabelSelect>
) : (
<div></div>
)
)}
</div>
)
}

View File

@@ -270,7 +270,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
{/* form */}
{
formShow && <div className="relative">
<div className="absolute left-0 border bottom-2 bg-background-login px-4 py-2 rounded-md w-[50%] min-w-80 z-50">
<div className="absolute left-0 bottom-2 bg-[#1a1a1a] px-4 py-2 rounded-md w-[50%] min-w-80 z-50">
{inputForm}
</div>
</div>
@@ -295,7 +295,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
<div className="flex absolute left-3 top-4 z-10">
{
form && <div
className={`w-6 h-6 rounded-sm hover:bg-gray-200 cursor-pointer flex justify-center items-center `}
className={`w-6 h-6 rounded-sm hover:bg-[#ffd025] hover:text-[#333] cursor-pointer flex justify-center items-center `}
onClick={() => (showWhenLocked || !inputLock.locked) && setFormShow(!formShow)}
><FormIcon className={!showWhenLocked && inputLock.locked ? 'text-gray-400' : 'text-gray-800'}></FormIcon></div>
}
@@ -338,7 +338,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
disabled={inputLock.locked}
onInput={handleTextAreaHeight}
placeholder={inputLock.locked ? inputLock.reason : t('chat.inputPlaceholder')}
className="questionTextarea w-full resize-none border-none bg-transparent outline-none max-h-[160px]"
className="questionTextarea w-full resize-none border-none bg-transparent outline-none max-h-[160px] pr-[68px]"
// className={"resize-none py-4 pr-10 text-md min-h-6 max-h-[200px] scrollbar-hide dark:bg-[#2A2B2E] text-gray-800" + (form && ' pl-10')}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {

View File

@@ -39,6 +39,7 @@ const colorList = [
]
export default function MessageBs({ data, onUnlike = () => { }, flow_type, onSource }: { data: ChatMessageType, flow_type: any, onUnlike?: any, onSource?: any }) {
// export default function MessageBs({ logo, data, onUnlike = () => { }, flow_type, onSource }: { logo: string, data: ChatMessageType, flow_type: any, onUnlike?: any, onSource?: any }) {
const avatarColor = colorList[
(data.sender?.split('').reduce((num, s) => num + s.charCodeAt(), 0) || 0) % colorList.length
]

View File

@@ -7,7 +7,8 @@ import robotU from "../../../assets/robotU.png";
import btnEdit from "../../../assets/chat/btn-edit.png";
import btnReSend from "../../../assets/chat/btn-reSend.png";
export default function MessageUser({ useName, data }: { data: ChatMessageType }) {
export default function MessageUser({ useName = 'xxx', data }: {useName: string, data: ChatMessageType }) {
// export default function MessageUser({ useName, data }: { data: ChatMessageType }) {
const msg = data.message[data.chatKey]
const { appConfig } = useContext(locationContext)

View File

@@ -0,0 +1,200 @@
import { Popover, PopoverContent, PopoverTrigger } from "@/components/bs-ui/popover";
import { useEffect, useMemo, useRef, useState } from "react";
import { SearchInput } from "@/components/bs-ui/input";
import { Checkbox } from "@/components/bs-ui/checkBox";
import { Label } from "@/components/bs-ui/label";
import { Pencil2Icon } from "@radix-ui/react-icons";
import { Trash2 } from "lucide-react";
import { Input } from "@/components/bs-ui/input";
import { useToast } from "@/components/bs-ui/toast/use-toast";
import { useTranslation } from "react-i18next";
import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
import { PlusIcon } from "@radix-ui/react-icons";
import { useContext } from "react";
import { userContext } from "@/contexts/userContext";
import {
createLabelApi, updateLabelApi,
createLinkApi, deleteLinkApi,
deleteLabelApi
} from "@/controllers/API/label";
import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
import biaoqianBian from "../../../assets/npc/biaoqian-bian.png"
import biaoqianDel from "../../../assets/npc/biaoqian-del.png"
export enum UPDATETYPE {
DELETELINK = 'deleteLink',
CREATELINK = 'createLink',
UPDATENAME = 'updateName',
CREATELABEL = 'createLabel',
DELETELABEL = 'deleteLabel'
}
export default function LabelSelect({ labels, all, children, resource, onUpdate }) {
const [open, setOpen] = useState(false)
const [data, setData] = useState([])
const { user } = useContext(userContext)
const dataRef = useRef([])
const { message } = useToast()
const { t } = useTranslation()
useEffect(() => {
const newData = all.map(d => {
const res = labels.find(l => l.value === d.value)
return res ? { ...d, selected: true } : d
})
dataRef.current = newData
setData(newData)
}, [all])
const handleEdit = (id) => {
setData(pre => pre.map(d => ({ ...d, edit: d.value === id })))
}
const handleChecked = (id) => {
const type = resource.type === 'assist' ? 3 : 2
setData(pre => {
const newData = pre.map(d => d.value === id ? { ...d, selected: !d.selected } : d)
const cur = newData.find(d => d.value === id)
captureAndAlertRequestErrorHoc(
(cur.selected ? createLinkApi(id, resource.id, type) : deleteLinkApi(id, resource.id, type)).then(() => {
onUpdate({
type: cur.selected ? UPDATETYPE.CREATELINK : UPDATETYPE.DELETELINK,
data: cur
})
})
)
return newData
})
}
const nameRef = useRef('')
const handleChange = (e, id) => {
nameRef.current = id ? dataRef.current.find(d => d.value === id).label : ''
setData(pre => pre.map(d => d.value === id ? { ...d, label: e.target.value } : d))
}
const errorRestName = (preName, id) => { //错误发生回退初值
preName
? setData(pre => pre.map(d => d.value === id ? { ...d, label: nameRef.current } : d))
: setData(pre => pre.filter(d => d.value))
}
const handleSave = async (e, id) => {
if (e.key === 'Enter') {
setData(pre => pre.map(d => d.value === id ? { ...d, edit: false } : d))
const label = data.find(d => d.value === id)
if (label.label.length > 10) {
errorRestName(nameRef.current, id)
return message({ title: t('prompt'), variant: 'warning', description: t('tag.labelMaxLength') })
}
const err = await captureAndAlertRequestErrorHoc(updateLabelApi(id, label.label).then((res: any) => {
setData(pre => {
const newData = pre.map(d => d.value ? d : { ...d, label: res.name, value: res.id })
dataRef.current = newData
return newData
})
onUpdate({
type: UPDATETYPE.UPDATENAME,
data: label
})
return message({ title: t('prompt'), variant: 'success', description: id ? t('updateSuccess') : t('createSuccess') })
}))
if (!err) {
errorRestName(nameRef.current, id)
}
}
}
const handleDelete = (label) => {
bsConfirm({
title: t('prompt'),
desc: t('tag.confirmDeleteLabel', { label: label.label }),
okTxt: "确认",
onOk(next) {
captureAndAlertRequestErrorHoc(deleteLabelApi(label.value).then(() => {
onUpdate({
type: UPDATETYPE.DELETELABEL,
data: label
})
message({ title: t('prompt'), variant: 'success', description: t('deleteSuccess') })
}))
next()
}
})
}
const handleOpenChange = (b) => { // 可用于整体保存
setOpen(b)
setData(pre => pre.map(d => ({ ...d, edit: false })))
}
const [keyword, setKeyword] = useState('')
const handleSearch = (e) => {
const key = e.target.value
setKeyword(key)
const newData = dataRef.current.filter(d => d.label.toUpperCase().includes(key.toUpperCase()))
setData(newData)
}
const handleAdd = () => {
if (keyword.length > 10) {
return message({ title: t('prompt'), variant: 'warning', description: t('tag.labelMaxLength') })
}
createLabelApi(keyword).then((res: any) => {
const addItem = { label: res.name, value: res.id, edit: false, selected: false }
dataRef.current = [addItem, ...dataRef.current]
setData([addItem])
onUpdate({
type: UPDATETYPE.CREATELABEL,
data: res.name
})
})
}
const showAdd = useMemo(() => {
if (data.length === 1 && data[0].label === keyword) {
return false
}
return true
}, [data])
return <Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
{children}
</PopoverTrigger>
<PopoverContent className="z-[20]" onClick={(e) => e.stopPropagation()}>
<div>
<SearchInput placeholder={t('chat.searchLabels')} value={keyword} onChange={handleSearch} className="w-[240px]"
onKeyDown={(e) => {
if (e.key === 'Enter') {
(!data.length && user.role === 'admin') ? handleAdd() : null
}
}} />
</div>
<div className="mt-4 h-[200px] overflow-y-auto relative biaoqianTab">
{data.map(d => <div className="flex group justify-between px-[10px] h-8 rounded-sm hover:bg-[#2E2406] ">
<div className="flex place-items-center space-x-2">
<Checkbox id={d.value} checked={d.selected} onCheckedChange={() => handleChecked(d.value)} />
{
d.edit
? <Input autoFocus className="h-6 bg-[#1a1a1a] text-[#fff]" type="text" value={d.label || ''}
onChange={(e) => handleChange(e, d.value)}
onKeyDown={(e) => handleSave(e, d.value)} />
: <Label htmlFor={d.value} className="cursor-pointer text-[#999999]">{d.label}</Label>
}
</div>
{user.role === 'admin' && <div className="flex place-items-center space-x-4 opacity-0 group-hover:opacity-100">
<img src={biaoqianBian} alt="" onClick={() => handleEdit(d.value)} className="w-[14px] cursor-pointer"/>
<img src={biaoqianDel} alt="" onClick={() => handleDelete(d)} className="w-[14px] ml-[14px] cursor-pointer"/>
{/* <Pencil2Icon className="cursor-pointer" onClick={() => handleEdit(d.value)} /> */}
{/* <Trash2 size={16} onClick={() => handleDelete(d)} className="text-gray-600 cursor-pointer" /> */}
</div>}
</div>)}
{(showAdd && keyword != '' && user.role === 'admin') && <div onClick={handleAdd}
className="absolute cursor-pointer w-[68px] h-[20px] border border-[#FFD025] text-[9px] text-[#FFD025] flex items-center justify-center right-0 bottom-0" style={{borderRadius:"3px"}}>
</div>}
</div>
</PopoverContent>
</Popover>
}

View File

@@ -1,7 +1,7 @@
import { Badge } from "@/components/bs-ui/badge";
import { Button } from "@/components/bs-ui/button";
import { getChatOnlineApi } from "@/controllers/API/assistant";
import { useEffect, useMemo, useRef, useState } from "react";
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { SearchInput } from "../../bs-ui/input";
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "../../bs-ui/sheet";
@@ -19,40 +19,57 @@ import zidingyi1 from "../../../assets/npc/zidingyi1.png";
import zidingyi2 from "../../../assets/npc/zidingyi2.png";
import npcIcon from "../../../assets/npc/npcIcon.png";
import nengliIcon from "../../../assets/npc/nengliIcon.png";
import biaoqianPaixu from "../../../assets/chat/biaoqian-paixu.png";
import { useDebounce } from "@/util/hook";
import LoadMore from "../loadMore";
import { userContext } from "@/contexts/userContext";
import { getHomeLabelApi } from "@/controllers/API/label";
import MarkLabel from "@/pages/ChatAppPage/components/MarkLabel";
export default function SkillChatSheet({ children, onSelect }) {
const [open, setOpen] = useState(false)
const { t } = useTranslation()
const { user } = useContext(userContext)
const chatListRef = useRef([])
const navigate = useNavigate()
const [labels, setLabels] = useState([])
const [open, setOpen] = useState(false)
const pageRef = useRef(1)
const [options, setOptions] = useState<any>([])
const searchRef = useRef('')
const [flag, setFlag] = useState(null) // 解决筛选之后再次发起请求覆盖筛选数据
const [keyword, setKeyword] = useState(' ')
const allDataRef = useRef([])
const [markLabelOpen, setMarkLabelOpen] = useState(false)
const pageRef = useRef(1)
const searchRef = useRef('')
const [options, setOptions] = useState<any>([])
const loadData = (more = false) => {
open && getChatOnlineApi(pageRef.current, searchRef.current).then(res => {
setOptions(opts => more ? [...opts, ...res] : res)
getChatOnlineApi(pageRef.current, searchRef.current, -1).then((res: any) => {
setFlag(true)
chatListRef.current = res
setOptions(more ? [...options, ...res] : res)
})
}
const debounceLoad = useDebounce(loadData, 600, false)
// useEffect(() => {
// // open && getChatOnlineApi().then(res => {
// // allDataRef.current = res
// // setKeyword('')
// // })
// // setKeyword(' ')
// pageRef.current = 1
// searchRef.current = ''
// loadData()
// }, [open])
useEffect(() => {
// open && getChatOnlineApi().then(res => {
// allDataRef.current = res
// setKeyword('')
// })
// setKeyword(' ')
pageRef.current = 1
searchRef.current = ''
loadData()
}, [open])
debounceLoad()
getHomeLabelApi().then((res: any) => {
setLabels(res.map(d => ({ label: d.name, value: d.id, selected: true })))
})
}, [])
const debounceLoad = useDebounce(loadData, 600, false)
// const options = useMemo(() => {
// return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase()))
@@ -64,9 +81,26 @@ export default function SkillChatSheet({ children, onSelect }) {
debounceLoad()
}
const handleLoadMore = () => {
const handleClose = async (bool) => {
const newHome = await getHomeLabelApi()
// @ts-ignore
setLabels(newHome.map(d => ({ label: d.name, value: d.id, selected: true })))
setMarkLabelOpen(bool)
}
const [chooseId, setChooseId] = useState() // 筛选项样式变化
const handleTagSearch = (id) => {
setChooseId(id)
setFlag(false)
pageRef.current = 1
getChatOnlineApi(pageRef.current, '', id).then((res: any) => {
setOptions(res)
})
}
const handleLoadMore = async () => {
pageRef.current++
loadData(true)
await debounceLoad(true)
}
const render = (item: any) => (
@@ -109,13 +143,31 @@ export default function SkillChatSheet({ children, onSelect }) {
<SheetTitle></SheetTitle>
<SheetDescription className="text-[#999999]">使线NPC或能力</SheetDescription>
{/* <SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} /> */}
<SearchInput placeholder="搜索" className="my-6" onChange={handleSearch} />
<SearchInput placeholder="搜索" className="my-6 npcInput3" onChange={handleSearch} />
<div className="flex flex-wrap duihuaTab">
{/* @ts-ignore */}
{user.role === 'admin' && <img src={biaoqianPaixu} alt="" className="h-[20px] w-[20px] cursor-pointer mr-[13px]" onClick={() => setMarkLabelOpen(true)} />}
{/* <Button variant={chooseId ? "outline" : "default"} className="mb-2 mr-5" size="sm"
onClick={() => { setChooseId(null); loadData(false) }}>全部</Button> */}
<div className={`h-[20px] px-[13px] mr-[13px] text-[9px] flex justify-center items-center border-radius-14 cursor-pointer ${chooseId ? "text-[#999999] bg-[#333333]" : "text-[#333333] bg-[#FFD54C]"}`} onClick={() => { setChooseId(null); loadData(false) }}></div>
{
labels.map((l, index) => index <= 11 &&
// <Button
// size="sm"
// onClick={() => handleTagSearch(l.value)}
// className="mr-4 mb-2" variant={l.value === chooseId ? "default" : "outline"}>{l.label}
// </Button>
<div className={`h-[20px] px-[13px] mb-[10px] mr-[13px] text-[9px] flex justify-center items-center border-radius-14 cursor-pointer ${l.value === chooseId ? "text-[#333333] bg-[#FFD54C]" : "text-[#999999] bg-[#333333]"}`} onClick={() => handleTagSearch(l.value)}>{l.label}</div>)
}
</div>
</div>
<div className="w-[690px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
<SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
<LoadMore onScrollLoad={handleLoadMore} />
{flag && <LoadMore onScrollLoad={handleLoadMore} />}
</div>
</div>
<MarkLabel open={markLabelOpen} home={labels} onClose={handleClose}></MarkLabel>
</SheetContent>
</Sheet>
};