1
This commit is contained in:
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"/><script src="/node_modules/ace-builds/src-min-noconflict/ace.js"></script><title>NPCs</title><script type="module" crossorigin src="/assets/index-b35a88c5.js"></script><link rel="modulepreload" crossorigin href="/assets/acebuilds-fbc0ccc6.js"><link rel="modulepreload" crossorigin href="/assets/reactflow-c250d835.js"><link rel="modulepreload" crossorigin href="/assets/reactdrop-be699031.js"><link rel="modulepreload" crossorigin href="/assets/pdfjs-36654f0a.js"><link rel="stylesheet" href="/assets/index-d15ff243.css"></head><body id="body" style="width:100%;height:100%"><noscript>You need to enable JavaScript to run this app.</noscript><div style="width:100vw;height:100vh" id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"/><script src="/node_modules/ace-builds/src-min-noconflict/ace.js"></script><title>NPCs</title><script type="module" crossorigin src="/assets/index-37491850.js"></script><link rel="modulepreload" crossorigin href="/assets/acebuilds-fbc0ccc6.js"><link rel="modulepreload" crossorigin href="/assets/reactflow-0cf1aa21.js"><link rel="modulepreload" crossorigin href="/assets/reactdrop-d9c7a74b.js"><link rel="modulepreload" crossorigin href="/assets/pdfjs-36654f0a.js"><link rel="stylesheet" href="/assets/index-59c5ed60.css"></head><body id="body" style="width:100%;height:100%"><noscript>You need to enable JavaScript to run this app.</noscript><div style="width:100vw;height:100vh" id="root"></div></body></html>
|
||||
@@ -356,22 +356,22 @@
|
||||
"evaluation": {
|
||||
"id": "任务ID",
|
||||
"filename": "测试文件名称",
|
||||
"skillAssistant": "技能助手",
|
||||
"skillAssistant": "能力NPC",
|
||||
"status": "状态",
|
||||
"score": "评测分数",
|
||||
"createDate": "创建日期",
|
||||
"download": "下载",
|
||||
"confirmDeleteEvaluation": "确认删除该评测任务?",
|
||||
"createTitle": "新建任务",
|
||||
"selectLabel": "选择要评测的技能或者助手:",
|
||||
"selectLabel": "选择要评测的能力或者NPC:",
|
||||
"selectPlaceholder": "请选择",
|
||||
"dataLabel": "测试集数据:",
|
||||
"fileExpandName": "支持扩展名:",
|
||||
"downloadTemplate": "下载模板文件",
|
||||
"promptLabel": "评测指令文本:",
|
||||
"enterExecType": "请选择要评测的技能或助手",
|
||||
"enterUniqueId": "请选择技能或助手ID",
|
||||
"enterVersion": "请选择技能的版本",
|
||||
"enterExecType": "请选择要评测的能力或NPC",
|
||||
"enterUniqueId": "请选择能力或NPCID",
|
||||
"enterVersion": "请选择能力的版本",
|
||||
"enterFile": "请选择测试集数据",
|
||||
"enterPrompt": "评测指令不能为空",
|
||||
"fileSizeLimit": "文件大小限制在10M以内",
|
||||
|
||||
@@ -186,7 +186,21 @@
|
||||
"fileStorageFailure": " 文件地址失效!",
|
||||
"confirmDeleteChat": "确认删除该会话?",
|
||||
"roundOver": "本轮结束",
|
||||
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动"
|
||||
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动",
|
||||
"feedback": "反馈",
|
||||
"feedbackRequired": "反馈信息不能为空",
|
||||
"dialogueSelection": "对话选择",
|
||||
"chooseSkillOrAssistant": "选择一个您想使用的线上能力或NPC",
|
||||
"search": "搜索",
|
||||
"recommendationQuestions": "推荐问题",
|
||||
"historicalMessages": "以上为历史消息",
|
||||
"clickDownload": "点击下载",
|
||||
"searchAssistantOrSkill": "搜索NPC或者能力",
|
||||
"operationTips": "操作提示:在左侧选择要展示的标签,在右侧拖拽进行排序",
|
||||
"selected": "已选",
|
||||
"pleaseSelectAnApp": "请选择一个应用",
|
||||
"allLabels": "全部标签",
|
||||
"searchLabels": "搜索标签"
|
||||
},
|
||||
"model": {
|
||||
"modelConfiguration": "模型配置",
|
||||
@@ -515,6 +529,11 @@
|
||||
"cancle": "取消",
|
||||
"tip": "提示",
|
||||
"deleteAssistant": "确认删除该NPC?",
|
||||
"chatTipsTitle": "使用提示",
|
||||
"updateSuccess": "修改成功",
|
||||
"createSuccess": "创建成功",
|
||||
"confirm": "确认",
|
||||
"required": "不可为空",
|
||||
"build": {
|
||||
"create": "创建",
|
||||
"assistant": "NPC",
|
||||
@@ -679,6 +698,12 @@
|
||||
"endDate": "结束日期",
|
||||
"actionBehavior": "操作行为"
|
||||
},
|
||||
"tag": {
|
||||
"labelMaxLength": "标签名不能超过10个字符",
|
||||
"confirmDeleteLabel": "标签【{{label}}】正在使用中,确认删除?",
|
||||
"createNewLabel": "创建“新标签”",
|
||||
"addLabel": "添加标签"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer": {
|
||||
"display_name": "AgentInitializer",
|
||||
|
||||
@@ -356,22 +356,22 @@
|
||||
"evaluation": {
|
||||
"id": "任务ID",
|
||||
"filename": "测试文件名称",
|
||||
"skillAssistant": "技能助手",
|
||||
"skillAssistant": "能力NPC",
|
||||
"status": "状态",
|
||||
"score": "评测分数",
|
||||
"createDate": "创建日期",
|
||||
"download": "下载",
|
||||
"confirmDeleteEvaluation": "确认删除该评测任务?",
|
||||
"createTitle": "新建任务",
|
||||
"selectLabel": "选择要评测的技能或者助手:",
|
||||
"selectLabel": "选择要评测的能力或者NPC:",
|
||||
"selectPlaceholder": "请选择",
|
||||
"dataLabel": "测试集数据:",
|
||||
"fileExpandName": "支持扩展名:",
|
||||
"downloadTemplate": "下载模板文件",
|
||||
"promptLabel": "评测指令文本:",
|
||||
"enterExecType": "请选择要评测的技能或助手",
|
||||
"enterUniqueId": "请选择技能或助手ID",
|
||||
"enterVersion": "请选择技能的版本",
|
||||
"enterExecType": "请选择要评测的能力或NPC",
|
||||
"enterUniqueId": "请选择能力或NPCID",
|
||||
"enterVersion": "请选择能力的版本",
|
||||
"enterFile": "请选择测试集数据",
|
||||
"enterPrompt": "评测指令不能为空",
|
||||
"fileSizeLimit": "文件大小限制在10M以内",
|
||||
|
||||
@@ -186,7 +186,21 @@
|
||||
"fileStorageFailure": " 文件地址失效!",
|
||||
"confirmDeleteChat": "确认删除该会话?",
|
||||
"roundOver": "本轮结束",
|
||||
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动"
|
||||
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动",
|
||||
"feedback": "反馈",
|
||||
"feedbackRequired": "反馈信息不能为空",
|
||||
"dialogueSelection": "对话选择",
|
||||
"chooseSkillOrAssistant": "选择一个您想使用的线上能力或NPC",
|
||||
"search": "搜索",
|
||||
"recommendationQuestions": "推荐问题",
|
||||
"historicalMessages": "以上为历史消息",
|
||||
"clickDownload": "点击下载",
|
||||
"searchAssistantOrSkill": "搜索NPC或者能力",
|
||||
"operationTips": "操作提示:在左侧选择要展示的标签,在右侧拖拽进行排序",
|
||||
"selected": "已选",
|
||||
"pleaseSelectAnApp": "请选择一个应用",
|
||||
"allLabels": "全部标签",
|
||||
"searchLabels": "搜索标签"
|
||||
},
|
||||
"model": {
|
||||
"modelConfiguration": "模型配置",
|
||||
@@ -515,6 +529,11 @@
|
||||
"cancle": "取消",
|
||||
"tip": "提示",
|
||||
"deleteAssistant": "确认删除该NPC?",
|
||||
"chatTipsTitle": "使用提示",
|
||||
"updateSuccess": "修改成功",
|
||||
"createSuccess": "创建成功",
|
||||
"confirm": "确认",
|
||||
"required": "不可为空",
|
||||
"build": {
|
||||
"create": "创建",
|
||||
"assistant": "NPC",
|
||||
@@ -679,6 +698,12 @@
|
||||
"endDate": "结束日期",
|
||||
"actionBehavior": "操作行为"
|
||||
},
|
||||
"tag": {
|
||||
"labelMaxLength": "标签名不能超过10个字符",
|
||||
"confirmDeleteLabel": "标签【{{label}}】正在使用中,确认删除?",
|
||||
"createNewLabel": "创建“新标签”",
|
||||
"addLabel": "添加标签"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer": {
|
||||
"display_name": "AgentInitializer",
|
||||
|
||||
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
BIN
src/assets/.DS_Store
vendored
BIN
src/assets/.DS_Store
vendored
Binary file not shown.
BIN
src/assets/chat/.DS_Store
vendored
BIN
src/assets/chat/.DS_Store
vendored
Binary file not shown.
BIN
src/assets/chat/biaoqian-paixu.png
Normal file
BIN
src/assets/chat/biaoqian-paixu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/chat/shengQue.png
Normal file
BIN
src/assets/chat/shengQue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
BIN
src/assets/npc/.DS_Store
vendored
BIN
src/assets/npc/.DS_Store
vendored
Binary file not shown.
BIN
src/assets/npc/biaoqian-bian.png
Normal file
BIN
src/assets/npc/biaoqian-bian.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/npc/biaoqian-del.png
Normal file
BIN
src/assets/npc/biaoqian-del.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/npc/tianjiabiaoqian.png
Normal file
BIN
src/assets/npc/tianjiabiaoqian.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/components/.DS_Store
vendored
BIN
src/components/.DS_Store
vendored
Binary file not shown.
110
src/components/bs-comp/cardComponent/LabelShow.tsx
Normal file
110
src/components/bs-comp/cardComponent/LabelShow.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
200
src/components/bs-comp/selectComponent/LabelSelect.tsx
Normal file
200
src/components/bs-comp/selectComponent/LabelSelect.tsx
Normal 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>
|
||||
}
|
||||
@@ -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>
|
||||
};
|
||||
|
||||
@@ -5,6 +5,6 @@ export const WordIcon = forwardRef<
|
||||
SVGSVGElement & { className: any },
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const _className = 'transition text-gray-950 ' + (className || '')
|
||||
const _className = 'transition text-[#43AFD2] ' + (className || '')
|
||||
return <Word ref={ref} {...props} className={_className} />;
|
||||
});
|
||||
|
||||
2
src/components/bs-icons/prompt/Prompt.svg
Normal file
2
src/components/bs-icons/prompt/Prompt.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg t="1721124907716" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6119"
|
||||
width="20" height="20"><path d="M366.8992 790.2208c0 22.528 18.432 40.96 40.96 40.96h208.384c22.528 0 40.96-18.432 40.96-40.96v-23.9616H366.8992v23.9616zM512 115.3024c-166.7072 0-301.8752 135.168-301.8752 301.8752 0 114.176 63.3856 213.4016 156.7744 264.8064v43.3152h290.304V681.984c93.3888-51.3024 156.7744-150.6304 156.7744-264.8064-0.1024-166.7072-135.2704-301.8752-301.9776-301.8752zM592.6912 862.3104H431.3088c-11.264 0-20.48 9.216-20.48 20.48v5.4272c0 11.264 9.216 20.48 20.48 20.48h161.28c11.264 0 20.48-9.216 20.48-20.48v-5.4272c0.1024-11.264-9.1136-20.48-20.3776-20.48z" p-id="6120" fill="#8a8a8a"></path></svg>
|
||||
|
After Width: | Height: | Size: 742 B |
10
src/components/bs-icons/prompt/index.tsx
Normal file
10
src/components/bs-icons/prompt/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { ReactComponent as Prompt } from "./Prompt.svg";
|
||||
|
||||
export const PromptIcon = forwardRef<
|
||||
SVGSVGElement & { className: any },
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const _className = 'transition text-[#999] ' + (className || '')
|
||||
return <Prompt ref={ref} {...props} className={_className} />;
|
||||
});
|
||||
@@ -35,7 +35,7 @@ const AlertDialogContent = React.forwardRef<
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-sm translate-x-[-50%] translate-y-[-50%] gap-10 border bg-background-prompt p-3 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 grid translate-x-[-50%] translate-y-[-50%] modal-box w-[400px] bg-[#262626] shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -64,7 +64,7 @@ const AlertDialogFooter = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cname(
|
||||
"flex justify-center flex-col-reverse sm:flex-row sm:space-x-2",
|
||||
"flex justify-center mt-[27px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -78,7 +78,7 @@ const AlertDialogTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cname("text-lg font-semibold text-center", className)}
|
||||
className={cname("text-[16px] font-bold text-center text-[#fff]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -90,7 +90,7 @@ const AlertDialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cname("text-sm text-muted-foreground text-center text-prompt-description", className)}
|
||||
className={cname("text-[12px] text-center mt-[18px] text-[#fff]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -103,7 +103,7 @@ const AlertDialogAction = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cname(buttonVariants({variant: "destructive"} ), className)}
|
||||
className={cname(buttonVariants({variant: "destructive"} ), className, "baogao-btn ml-[27px]")}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -118,7 +118,8 @@ const AlertDialogCancel = React.forwardRef<
|
||||
className={cname(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
className
|
||||
className,
|
||||
"baogao-btn"
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MinusCircledIcon } from "@radix-ui/react-icons"
|
||||
import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons"
|
||||
import { useState } from "react"
|
||||
|
||||
import sousuo from "../../../assets/npc/sousuo1.png"
|
||||
import sousuo from "../../../assets/npc/sousuo.png"
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> { }
|
||||
|
||||
|
||||
@@ -13,10 +13,18 @@ export interface AssistantItemDB {
|
||||
status: number;
|
||||
}
|
||||
// 获取助手列表
|
||||
export const getAssistantsApi = async (page, limit, name): Promise<AssistantItemDB[]> => {
|
||||
// export const getAssistantsApi = async (page, limit, name): Promise<AssistantItemDB[]> => {
|
||||
// return await axios.get(`/api/v1/assistant`, {
|
||||
// params: {
|
||||
// page, limit, name
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
export const getAssistantsApi = async (page, limit, name, tag_id): Promise<AssistantItemDB[]> => {
|
||||
return await axios.get(`/api/v1/assistant`, {
|
||||
params: {
|
||||
page, limit, name
|
||||
page, limit, name,
|
||||
tag_id: tag_id === -1 ? null : tag_id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -148,8 +148,13 @@ export async function saveFlowToDatabase(newFlow: {
|
||||
* @returns {Promise<any>} The flows data.
|
||||
* @throws Will throw an error if reading fails.
|
||||
*/
|
||||
export async function readFlowsFromDatabase(page: number = 1, pageSize: number = 20, search: string) {
|
||||
const { data, total }: { data: any[], total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${pageSize}&name=${search}`);
|
||||
// export async function readFlowsFromDatabase(page: number = 1, pageSize: number = 20, search: string) {
|
||||
// const { data, total }: { data: any[], total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${pageSize}&name=${search}`);
|
||||
// return { data, total };
|
||||
// }
|
||||
export async function readFlowsFromDatabase(page: number = 1, pageSize: number = 20, search: string, tag_id = -1) {
|
||||
const tagIdStr = tag_id === -1 ? '' : `&tag_id=${tag_id}`
|
||||
const { data, total }: { data: any[], total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${pageSize}&name=${search}${tagIdStr}`);
|
||||
return { data, total };
|
||||
}
|
||||
|
||||
|
||||
61
src/controllers/API/label.ts
Normal file
61
src/controllers/API/label.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import axios from "../request";
|
||||
|
||||
export async function getAllLabelsApi() {
|
||||
return await axios.get('/api/v1/tag')
|
||||
}
|
||||
|
||||
// admin全局创建一个标签
|
||||
export async function createLabelApi(name:string) {
|
||||
return await axios.post('/api/v1/tag', {
|
||||
name
|
||||
})
|
||||
}
|
||||
|
||||
// admin修改标签
|
||||
export async function updateLabelApi(id:number, name:string) {
|
||||
return await axios.put('/api/v1/tag', {
|
||||
tag_id: id,
|
||||
name
|
||||
})
|
||||
}
|
||||
|
||||
// admin删除标签
|
||||
export async function deleteLabelApi(id:number) {
|
||||
return await axios.delete('/api/v1/tag', {
|
||||
data: {
|
||||
tag_id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 建立助手或技能和标签的关系,即选择标签
|
||||
export async function createLinkApi(tag_id:number, resource_id:string, resource_type:number) {
|
||||
return await axios.post('/api/v1/tag/link', {
|
||||
tag_id,
|
||||
resource_id,
|
||||
resource_type
|
||||
})
|
||||
}
|
||||
|
||||
// 删除助手或技能和标签的关系,即不选标签
|
||||
export async function deleteLinkApi(tag_id:number, resource_id:string, resource_type:number) {
|
||||
return await axios.delete('/api/v1/tag/link', {
|
||||
data: {
|
||||
tag_id,
|
||||
resource_id,
|
||||
resource_type
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取首页展示的标签列表
|
||||
export async function getHomeLabelApi() {
|
||||
return await axios.get('/api/v1/tag/home')
|
||||
}
|
||||
|
||||
// 更新首页展示的标签列表
|
||||
export async function updateHomeLabelApi(tag_ids) {
|
||||
return await axios.post('/api/v1/tag/home', {
|
||||
tag_ids
|
||||
})
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export default function MainLayout() {
|
||||
}
|
||||
|
||||
{
|
||||
isMenu('filelib') &&
|
||||
isMenu('knowledge') &&
|
||||
<NavLink to='/filelib' className="nav-link3 w-[70px] h-[55px] mx-[7px]">
|
||||
{/* <HardDrive /> */}
|
||||
<span className="mx-3 max-w-[48px]">{t('menu.knowledge')}</span>
|
||||
|
||||
158
src/pages/ChatAppPage/components/ChatHome.tsx
Normal file
158
src/pages/ChatAppPage/components/ChatHome.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Badge } from "@/components/bs-ui/badge";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { getChatOnlineApi } from "@/controllers/API/assistant";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SearchInput } from "@/components/bs-ui/input";
|
||||
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "@/components/bs-ui/sheet";
|
||||
import CardComponent, { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import { SkillIcon } from "@/components/bs-icons/skill";
|
||||
import { AssistantIcon } from "@/components/bs-icons/assistant";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
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 sousuo from "../../../assets/npc/sousuo.png";
|
||||
import shengQue from "../../../assets/chat/shengQue.png";
|
||||
import { useDebounce } from "@/util/hook";
|
||||
import LoadMore from "@/components/bs-comp/loadMore";
|
||||
import { userContext } from "@/contexts/userContext";
|
||||
import { getHomeLabelApi } from "@/controllers/API/label";
|
||||
import MarkLabel from "@/pages/ChatAppPage/components/MarkLabel";
|
||||
|
||||
export default function HomePage({ onSelect }) {
|
||||
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 loadData = (more = false) => {
|
||||
getChatOnlineApi(pageRef.current, searchRef.current, -1).then((res: any) => {
|
||||
setFlag(true)
|
||||
chatListRef.current = res
|
||||
setOptions(more ? [...options, ...res] : res)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
debounceLoad()
|
||||
getHomeLabelApi().then((res: any) => {
|
||||
setLabels(res.map(d => ({ label: d.name, value: d.id, selected: true })))
|
||||
})
|
||||
}, [])
|
||||
|
||||
const debounceLoad = useDebounce(loadData, 600, false)
|
||||
|
||||
const handleSearch = (e) => {
|
||||
pageRef.current = 1
|
||||
searchRef.current = e.target.value
|
||||
debounceLoad()
|
||||
}
|
||||
|
||||
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++
|
||||
await debounceLoad(true)
|
||||
}
|
||||
|
||||
const render = (item: any) => (
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox relative`} onClick={() => { onSelect(item); setOpen(false) }}>
|
||||
<div className="npcInfoItemBg">
|
||||
<span>
|
||||
<span>
|
||||
<div>
|
||||
<TitleIconBg className="w-[160px] h-[160px] min-w-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<TitleIconBg className="w-[40px] h-[40px] min-w-[40px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-[10px] test-[13px]">{item.desc}</p>
|
||||
<div className={`absolute right-0 top-0 w-[41px] h-[16px] flex justify-center items-center text-[9px] ${item.flow_type === 'flow' ? 'text-[#333333] bg-[#FFD54C]' : 'text-[#FFFFFF] bg-[#2586FF]'}`} style={{borderRadius:"0px 10px 0px 7px",fontWeight:"bold"}}>
|
||||
{item.flow_type === 'flow' ? '能力' : 'NPC'}
|
||||
</div>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return <div className="h-[100vh]" style={{width: 'calc(100% - 288px)'}}>
|
||||
<div className="w-[100%] h-full xinDuiHua-box" onClick={e => e.stopPropagation()}>
|
||||
{/* <div className="xinDuiHua-boxR">
|
||||
</div> */}
|
||||
<div className="p-6 flex justify-center items-center" style={{flexDirection:"column"}}>
|
||||
{/* <SheetTitle>选择对话</SheetTitle>
|
||||
<SheetDescription className="text-[#999999]">选择一个您想使用的上线NPC或能力</SheetDescription> */}
|
||||
{/* <SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} /> */}
|
||||
<p className="text-[#FFFFFF] text-[12px] mt-[30px]">选择对话</p>
|
||||
<p className="text-[#999999] text-[11px] mt-[10px]">选择一个您想使用的上线NPC或能力</p>
|
||||
<div className="relative my-6">
|
||||
<SearchInput placeholder="搜索" className="npcInput3 w-[610px] chatHomeInput" onChange={handleSearch} />
|
||||
<div className="absolute w-[54px] h-[34px] bg-[#FFD54C] right-0 top-0 border-radius-35 flex justify-center items-center cursor-pointer" onClick={handleSearch}><img src={sousuo} className="w-[14px]" alt="" /></div>
|
||||
</div>
|
||||
<div className="flex flex-wrap duihuaTab w-[610px]">
|
||||
{/* @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-[#1A1A1A]" : "text-[#FFD54C] bg-[#4D4017]"}`} onClick={() => { setChooseId(null); loadData(false) }}>全部</div>
|
||||
{
|
||||
labels.map((l, index) => index <= 11 &&
|
||||
<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-[#FFD54C] bg-[#4D4017]" : "text-[#999999] bg-[#1A1A1A]"}`} onClick={() => handleTagSearch(l.value)}>{l.label}</div>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-y-auto scrollbar-hide skillSheet">
|
||||
{options.length ? <SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
|
||||
:<div className="flex justify-center items-center" style={{flexDirection:"column",height:"calc(100% - 210px"}}>
|
||||
<img src={shengQue} className="w-[176px]" alt="" />
|
||||
<p className="mt-[26px] text-[#666666] text-[11px]">未找到您搜索的「NPC」或「能力」</p>
|
||||
</div>}
|
||||
{flag && <LoadMore onScrollLoad={handleLoadMore} />}
|
||||
</div>
|
||||
<MarkLabel open={markLabelOpen} home={labels} onClose={handleClose}></MarkLabel>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -184,6 +184,7 @@ export default function ChatPanne({ customWsHost = '', appendHistory = false, da
|
||||
});
|
||||
document.dispatchEvent(myEvent);
|
||||
}
|
||||
if(!appConfig.websocketHost) return
|
||||
// // ws通信
|
||||
// const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
|
||||
// // 停止状态
|
||||
|
||||
146
src/pages/ChatAppPage/components/MarkLabel.tsx
Normal file
146
src/pages/ChatAppPage/components/MarkLabel.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { PromptIcon } from '@/components/bs-icons/prompt';
|
||||
import { Button } from '@/components/bs-ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||
import { cname } from '@/components/bs-ui/utils';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { updateHomeLabelApi, getAllLabelsApi } from "@/controllers/API/label";
|
||||
import { captureAndAlertRequestErrorHoc } from '@/controllers/request';
|
||||
import { useToast } from '@/components/bs-ui/toast/use-toast';
|
||||
|
||||
function DragItem({className = '', data, children, onCancel}) {
|
||||
return <div className={cname('h-7 w-32 relative rounded-xl border flex place-items-center border-[#666] text-[#999]', className)}>
|
||||
<CrossCircledIcon onClick={(e) => {e.stopPropagation(); onCancel(data.id)}}
|
||||
className='text-[#999] absolute top-[-6px] right-[-6px] cursor-pointer'/>
|
||||
<div className='bg-[#999] rounded-xl w-[26px] h-full text-center ml-[-1px]'>
|
||||
<span className='text-slate-50 font-bold text-sm text-[#262626]'>{data.index}</span>
|
||||
</div>
|
||||
<div className='ml-2 truncate'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default function MarkLabel({open, home, onClose}) {
|
||||
const { t } = useTranslation()
|
||||
const [labels, setLabels] = useState([])
|
||||
const [selected, setSelected] = useState([])
|
||||
const { message } = useToast()
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
const all = await getAllLabelsApi()
|
||||
const newData = all.data.map(d => {
|
||||
const res = home.find(h => h.value === d.id)
|
||||
return res ? {label:d.name, value:d.id, selected:true} : {label:d.name, value:d.id, selected:false}
|
||||
})
|
||||
setLabels(newData)
|
||||
setSelected(home)
|
||||
}
|
||||
init()
|
||||
}, [home])
|
||||
|
||||
const handleCancel = () => {
|
||||
onClose(false)
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
await captureAndAlertRequestErrorHoc(updateHomeLabelApi(selected.map(s => s.value)))
|
||||
onClose(false)
|
||||
}
|
||||
|
||||
const handleSelect = (id) => {
|
||||
setLabels(pre => {
|
||||
const newData = pre.map(l => l.value === id ? {...l, selected:!l.selected} : l)
|
||||
if(newData.filter(d => d.selected).length > 10) {
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'warning',
|
||||
description: '最多选择10个标签'
|
||||
})
|
||||
return pre
|
||||
}
|
||||
const select = newData.find(d => d.value === id && d.selected)
|
||||
setSelected(select ? [...selected, select] : pre => pre.filter(d => d.value !== id))
|
||||
return newData
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (id) => {
|
||||
setSelected(pre => pre.filter(d => d.value !== id))
|
||||
setLabels(pre => pre.map(d => d.value === id ? {...d, selected:!d.selected} : d))
|
||||
}
|
||||
|
||||
const handleDragEnd = (result) => {
|
||||
if(!result.destination) return
|
||||
const newData = selected
|
||||
const [moveItem] = newData.splice(result.source.index, 1)
|
||||
newData.splice(result.destination.index, 0, moveItem)
|
||||
setSelected(newData)
|
||||
setFlag(false)
|
||||
}
|
||||
|
||||
const [flag, setFlag] = useState(false) // 解决拖拽映射位置错位
|
||||
|
||||
return <Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className=' max-w-[70%]'>
|
||||
<DialogHeader>
|
||||
<DialogTitle className='flex items-center space-x-2'>
|
||||
<PromptIcon/>
|
||||
<span className='text-sm text-[#999]'>{t('chat.operationTips')}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className='h-[650px] w-full grid grid-cols-[70%_30%]'>
|
||||
<div className='ml-[10px]'>
|
||||
<div className='flex w-[760px] flex-wrap'>
|
||||
{/* <div className='w-full relative top-[30px] transform -translate-y-[50%] flex'> */}
|
||||
{
|
||||
labels.map(l =>
|
||||
<Button onClick={() => handleSelect(l.value)}
|
||||
size='sm'
|
||||
className={`ml-[14px] mb-[10px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333] hover:bg-[#333333]' : 'text-[#333333] bg-[#FFD54C] hover:bg-[#FFD54C]'} w-[120px]`}>
|
||||
<span className='truncate'>{l.label}</span>
|
||||
</Button>
|
||||
// <div onClick={() => handleSelect(l.value)} style={{borderRadius:"4px"}} className={`ml-[14px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333]' : 'text-[#333333] bg-[#FFD54C]'} w-[120px]`}>
|
||||
// {l.label}
|
||||
// </div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-l border-[#666]'>
|
||||
<div className='ml-4'>
|
||||
<span className='text-md font-bold text-[#999]'>{t('chat.selected')}:{selected.length}/10</span>
|
||||
<DragDropContext onDragEnd={handleDragEnd} onDragStart={() => setFlag(true)} onDragUpdate={() => setFlag(true)}>
|
||||
<Droppable droppableId={'list'}>
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{selected.map((b,index) => (
|
||||
<Draggable key={'drag' + b.value} draggableId={'drag' + b.value} index={index}>
|
||||
{(provided) => (
|
||||
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}
|
||||
style={flag ? { ...provided.draggableProps.style, position:'relative', left:0, top:0 } : {...provided.draggableProps.style}}>
|
||||
<DragItem onCancel={handleDelete} data={{index:index + 1, id:b.value}} className='mt-4 w-[170px]'>
|
||||
<span className='font-bold text-sm'>{b.label}</span>
|
||||
</DragItem>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className='absolute bottom-6 right-6'>
|
||||
<Button variant="outline" className="h-10 w-[120px] px-16" onClick={handleCancel}>{t('cancel')}</Button>
|
||||
<Button className="px-16 h-10 w-[120px] bg-[#FFD54C] hover:bg-[#FFD54C]" onClick={handleConfirm}>{t('save')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
|
||||
import { Download, Import } from "lucide-react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSourceChunksApi, splitWordApi } from "../../../controllers/API";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import FileView, { checkSassUrl } from "./FileView";
|
||||
import { downloadFile } from "../../../util/utils";
|
||||
import FileView, { checkSassUrl } from "./FileView";
|
||||
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/bs-ui/tooltip";
|
||||
|
||||
// 顶部答案区
|
||||
const Anwser = ({ id, msg, onInit, onAdd }) => {
|
||||
@@ -14,12 +16,20 @@ const Anwser = ({ id, msg, onInit, onAdd }) => {
|
||||
// init
|
||||
useEffect(() => {
|
||||
onInit([])
|
||||
msg && splitWordApi(msg, id).then((res) => {
|
||||
// 匹配
|
||||
const reg = new RegExp(`(${res.join('|')})`, 'g')
|
||||
setHtml(msg.replace(reg, '<span>$1</span>'))
|
||||
onInit(res)
|
||||
})
|
||||
const loadData = () => {
|
||||
splitWordApi(msg, id).then((res) => {
|
||||
// 匹配
|
||||
const reg = new RegExp(`(${res.join('|')})`, 'g')
|
||||
setHtml(msg.replace(reg, '<span>$1</span>'))
|
||||
onInit(res)
|
||||
}).catch(e => {
|
||||
// 自动重试
|
||||
e === '后台处理中,稍后再试' && setTimeout(() => {
|
||||
loadData()
|
||||
}, 1800);
|
||||
})
|
||||
}
|
||||
msg && loadData()
|
||||
}, [])
|
||||
|
||||
// add
|
||||
@@ -33,18 +43,43 @@ const Anwser = ({ id, msg, onInit, onAdd }) => {
|
||||
return () => pRef.current?.removeEventListener('click', handleclick)
|
||||
}, [])
|
||||
|
||||
return <div className="bg-gray-100 rounded-md py-4 px-2 max-h-24 overflow-y-auto">
|
||||
return <div className="bg-gray-100 dark:bg-[#3C4048] rounded-md py-4 px-2 max-h-24 overflow-y-auto">
|
||||
<p ref={pRef} className="anwser-souce" dangerouslySetInnerHTML={{ __html: html }}></p>
|
||||
</div>
|
||||
}
|
||||
|
||||
//
|
||||
let timer = null
|
||||
const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId: string, words: string[], data: ChatMessageType, onClose: any, onAdd: any, children: any }) => {
|
||||
const ResultPanne = ({ chatId, words, data, onClose, onAdd, children, closeDialog }: { chatId: string, words: string[], data: any, onClose: any, onAdd: any, children: any, closeDialog: () => void }) => {
|
||||
const { t } = useTranslation()
|
||||
const [editCustomKey, setEditCustomKey] = useState(false)
|
||||
const inputRef = useRef(null)
|
||||
|
||||
// 移动端
|
||||
const [collapse, setCollapse] = useState(true)
|
||||
const [isMobile, setIsMobile] = useState(true)
|
||||
const [width, setWidth] = useState(window.innerWidth);
|
||||
const [height, setHeight] = useState(window.innerHeight);
|
||||
const checkIsMobile = () => {
|
||||
if (width < 640) {
|
||||
setIsMobile(true)
|
||||
} else {
|
||||
setIsMobile(false)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setWidth(window.innerWidth);
|
||||
setHeight(window.innerHeight);
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
checkIsMobile()
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
}
|
||||
}, [width])
|
||||
// 移动端 e
|
||||
|
||||
const handleAddKeyword = (str: string) => {
|
||||
setEditCustomKey(false)
|
||||
if (!str) return
|
||||
@@ -59,7 +94,7 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
|
||||
// if (!words.length) return setFiles([])
|
||||
clearTimeout(timer) // 简单防抖
|
||||
timer = setTimeout(() => {
|
||||
getSourceChunksApi(chatId, data.id, words.join(';')).then((_files) => {
|
||||
getSourceChunksApi(chatId, data.messageId, words.join(';')).then((_files) => {
|
||||
setFiles(_files)
|
||||
// 默认打开第一个文件
|
||||
_files && setFile(_files[0])
|
||||
@@ -78,70 +113,95 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
|
||||
setTimeout(() => document.getElementById('taginput')?.focus(), 0);
|
||||
}
|
||||
|
||||
return <div className="flex gap-4 mt-4" style={{ height: 'calc(100vh - 10rem)' }}>
|
||||
return <div className="flex gap-4 mt-4" style={!isMobile ? { height: 'calc(100vh - 10rem)' } : { height: 'calc(100vh - 4rem)' }}>
|
||||
{
|
||||
isMobile && <div className="absolute top-2 left-4 z-10 bg-gray-100 dark:bg-gray-950 py-1 px-2 pb-2 rounded-md">
|
||||
{!collapse && <span onClick={() => { setCollapse(true) }} className="">收起</span>}
|
||||
{collapse && <span onClick={() => { setCollapse(false) }} className="">展开</span>}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
isMobile && <div className="absolute top-2 right-4 z-10 bg-gray-100 dark:bg-gray-950 py-1 px-2 pb-2 rounded-md">
|
||||
<span onClick={closeDialog} >关闭</span>
|
||||
</div>
|
||||
}
|
||||
{/* left */}
|
||||
<div className="w-[300px] bg-gray-100 rounded-md py-4 px-2 h-full overflow-y-auto no-scrollbar">
|
||||
{/* label */}
|
||||
<div className="mb-4 text-sm font-bold">
|
||||
{t('chat.filterLabel')}
|
||||
<div className="tooltip fixed" data-tip={t('chat.tooltipText')}><span data-theme="light" className="badge cursor-pointer">?</span></div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{words.map((str, i) => <div key={str} className="badge badge-info h-[auto] gap-2 text-gray-600 bg-[rgba(53,126,249,.15)]">{str}<span className="cursor-pointer" onClick={() => onClose(i)}>x</span></div>)}
|
||||
{/* 自定义 */}
|
||||
{
|
||||
editCustomKey ? <div className="badge badge-info gap-2 cursor-pointer bg-[rgba(53,126,249,.15)]"><input ref={inputRef} id="taginput" className="w-20 h-4 py-0 border-none outline-none bg-gray-50"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
{
|
||||
(!isMobile || !collapse) && <div className="sm:w-[300px] bg-gray-100 dark:bg-[#3C4048] rounded-md py-4 px-2 h-full overflow-y-auto no-scrollbar w-[200px] max-h-[100%] sm:max-h-full absolute sm:static z-20 sm:z-auto">
|
||||
{/* label */}
|
||||
<div className="mb-4 text-sm font-bold place-items-center space-x-1 hidden sm:block">
|
||||
<div className="flex">
|
||||
<span>{t('chat.filterLabel')}</span>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkCircledIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="w-[170px] break-words">{t('chat.tooltipText')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 hidden sm:block">
|
||||
{words.map((str, i) => <div key={str} className="badge badge-info h-[auto] gap-2 text-gray-600 bg-[rgba(53,126,249,.15)] dark:text-slate-50">{str}<span className="cursor-pointer font-thin" onClick={() => onClose(i)}>x</span></div>)}
|
||||
{
|
||||
editCustomKey ? <div className="badge badge-info gap-2 cursor-pointer bg-[rgba(53,126,249,.15)]"><input ref={inputRef} id="taginput" className="w-20 h-4 py-0 border-none outline-none bg-gray-50"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
handleAddKeyword(inputRef.current.value);
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
handleAddKeyword(inputRef.current.value);
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
handleAddKeyword(inputRef.current.value);
|
||||
}}></input></div> :
|
||||
<div className="badge badge-info gap-2 cursor-pointer bg-[rgba(53,126,249,.86)] text-gray-50" onClick={handleOpenInput}><span>{t('chat.addCustomLabel')}</span></div>
|
||||
}
|
||||
</div>
|
||||
{/* files */}
|
||||
<div className="mt-4">
|
||||
<p className="mb-4 text-sm font-bold">{t('chat.sourceDocumentsLabel')}</p>
|
||||
{files.map(_file =>
|
||||
_file.right ? <div key={_file.id} onClick={() => setFile(_file)} className={`group rounded-xl bg-[#fff] hover-bg-gray-200 flex items-center px-4 mb-2 relative min-h-16 cursor-pointer ${file?.id === _file.id && 'bg-gray-200'}`}>
|
||||
<p className="text-sm break-all">{_file.fileName}</p>
|
||||
<div className="absolute right-1 top-1 gap-2 hidden group-hover:flex">
|
||||
{
|
||||
_file.fileUrl && <div className="tooltip" data-tip={t('chat.downloadPDFTooltip')}>
|
||||
<a href="javascript:;" onClick={(event) => { downloadFile(checkSassUrl(_file.fileUrl), _file.fileName.replace(/\.[\w\d]+$/, '.pdf')); event.stopPropagation() }} >
|
||||
<Import color="rgba(53,126,249,1)" size={22} strokeWidth={1.5}></Import>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
_file.originUrl && <div className="tooltip tooltip-left" data-tip={t('chat.downloadOriginalTooltip')}>
|
||||
<a href="javascript:;" onClick={(event) => { downloadFile(checkSassUrl(_file.originUrl), _file.fileName); event.stopPropagation() }} >
|
||||
<Download color="rgba(53,126,249,1)" size={20} strokeWidth={1.5}></Download>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span className="absolute right-1 bottom-1 text-blue-400 text-sm">{_file.score}</span>
|
||||
</div> :
|
||||
<div key={_file.id} className={`msk group rounded-xl bg-[#fff] hover-bg-gray-200 flex items-center px-4 mb-2 relative min-h-16 cursor-pointer ${file?.id === _file.id && 'bg-gray-200'}`}>
|
||||
<p className="text-sm blur-sm">是真的马赛克.msk</p>
|
||||
}}></input></div> :
|
||||
<div className="badge badge-info gap-2 cursor-pointer bg-[rgba(53,126,249,.86)] text-gray-50" onClick={handleOpenInput}><span>{t('chat.addCustomLabel')}</span></div>
|
||||
}
|
||||
</div>
|
||||
{/* files */}
|
||||
<div className="mt-4">
|
||||
<p className="mb-4 text-sm font-bold">{t('chat.sourceDocumentsLabel')}</p>
|
||||
{files.map(_file =>
|
||||
_file.right ? <div key={_file.id} onClick={() => setFile(_file)} className={`group rounded-xl bg-[#fff] dark:bg-[#303134] hover-bg-gray-200 flex items-center px-4 mb-2 relative min-h-16 cursor-pointer ${file?.id === _file.id && 'bg-gray-200'}`}>
|
||||
<p className="text-sm break-all">{_file.fileName}</p>
|
||||
<div className="absolute right-1 top-1 gap-2 hidden group-hover:flex">
|
||||
{
|
||||
_file.fileUrl && <div className="tooltip" data-tip={t('chat.downloadPDFTooltip')}>
|
||||
<a href="javascript:;" onClick={(event) => { downloadFile(checkSassUrl(_file.fileUrl), _file.fileName.replace(/\.[\w\d]+$/, '.pdf')); event.stopPropagation() }} >
|
||||
<Import color="rgba(53,126,249,1)" size={22} strokeWidth={1.5}></Import>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
_file.originUrl && <div className="tooltip tooltip-left" data-tip={t('chat.downloadOriginalTooltip')}>
|
||||
<a href="javascript:;" onClick={(event) => { downloadFile(checkSassUrl(_file.originUrl), _file.fileName); event.stopPropagation() }} >
|
||||
<Download color="rgba(53,126,249,1)" size={20} strokeWidth={1.5}></Download>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span className="absolute right-1 bottom-1 text-blue-400 text-sm">{_file.score}</span>
|
||||
</div>
|
||||
)}
|
||||
{!files.length && <p className="text-sm text-center mt-10 text-gray-500">{t('chat.noMatchedFilesMessage')}</p>}
|
||||
</div> :
|
||||
<div key={_file.id} className={`msk group rounded-xl bg-[#fff] hover-bg-gray-200 flex items-center px-4 mb-2 relative min-h-16 cursor-pointer ${file?.id === _file.id && 'bg-gray-200'}`}>
|
||||
<p className="text-sm blur-sm">是真的马赛克.msk</p>
|
||||
<span className="absolute right-1 bottom-1 text-blue-400 text-sm">{_file.score}</span>
|
||||
</div>
|
||||
)}
|
||||
{!files.length && <p className="text-sm text-center mt-10 text-gray-500">{t('chat.noMatchedFilesMessage')}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{/* file pane */}
|
||||
{file && children(file)}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default function ResouceModal({ chatId, data, open, setOpen }: { chatId: string, data: ChatMessageType, open: boolean, setOpen: (b: boolean) => void }) {
|
||||
const ResouceModal = forwardRef((props, ref) => {
|
||||
// labels
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [keywords, setKeywords] = useState([])
|
||||
const handleAddWord = (word: string) => {
|
||||
// 去重 更新
|
||||
@@ -152,38 +212,35 @@ export default function ResouceModal({ chatId, data, open, setOpen }: { chatId:
|
||||
setKeywords(keywords.filter((wd, i) => i !== index))
|
||||
}
|
||||
|
||||
// 记忆
|
||||
// useEffect(() => {
|
||||
// const KEYWORDS_LOCAL_KEY = 'KEYWORDS_LOCAL'
|
||||
// setKeywords(JSON.parse(localStorage.getItem(KEYWORDS_LOCAL_KEY) || '[]'))
|
||||
const [data, setData] = useState<any>({})
|
||||
useImperativeHandle(ref, () => ({
|
||||
openModal: (data) => {
|
||||
setOpen(true)
|
||||
setData(data)
|
||||
}
|
||||
}));
|
||||
|
||||
// return () => localStorage.setItem(KEYWORDS_LOCAL_KEY, JSON.stringify(keywords))
|
||||
// }, [])
|
||||
const MemoizedFileView = React.memo(FileView);
|
||||
|
||||
return <dialog className={`modal bg-blur-shared ${open ? 'modal-open' : 'modal-close'}`} onClick={() => setOpen(false)}>
|
||||
<div className=" rounded-xl px-4 py-6 bg-[#fff] shadow-lg dark:bg-background w-[80%]" onClick={e => e.stopPropagation()}>
|
||||
return <Dialog open={open} onOpenChange={setOpen} >
|
||||
<DialogContent className="min-w-[80%]">
|
||||
{/* <DialogHeader>
|
||||
<DialogTitle>{t('chat.feedback')}</DialogTitle>
|
||||
</DialogHeader> */}
|
||||
{open && <div>
|
||||
<Anwser id={data.id} msg={data.message || data.thought} onInit={setKeywords} onAdd={handleAddWord}></Anwser>
|
||||
<ResultPanne words={keywords} chatId={chatId} data={data} onClose={handleDelKeyword} onAdd={handleAddWord}>
|
||||
<Anwser id={data.messageId} msg={data.message} onInit={setKeywords} onAdd={handleAddWord}></Anwser>
|
||||
<ResultPanne words={keywords} chatId={data.chatId} data={data} onClose={handleDelKeyword} onAdd={handleAddWord} closeDialog={()=>setOpen(false)}>
|
||||
{
|
||||
(file) => file.fileUrl ?
|
||||
<MemoizedFileView data={file}></MemoizedFileView> :
|
||||
<div className="flex-1 bg-gray-100 rounded-md text-center">
|
||||
<div className="flex-1 bg-gray-100 dark:bg-[#3C4048] rounded-md text-center">
|
||||
<p className="text-gray-500 text-md mt-[40%]">{t('chat.fileStorageFailure')}</p>
|
||||
</div>
|
||||
}
|
||||
</ResultPanne>
|
||||
</div>}
|
||||
</div>
|
||||
</dialog>
|
||||
};
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
});
|
||||
|
||||
// const useRefState = (state) => {
|
||||
// const [data, setData] = useState(state)
|
||||
// const ref = useRef(state)
|
||||
// return [data, ref, (nState) => {
|
||||
// setData(nState)
|
||||
// ref.current = nState
|
||||
// }]
|
||||
// }
|
||||
export default ResouceModal
|
||||
|
||||
@@ -22,8 +22,12 @@ import SkillChatSheet from "@/components/bs-comp/sheets/SkillChatSheet";
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import npcIcon from "../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../assets/npc/nengliIcon.png";
|
||||
import xinjian1 from "../../assets/chat/xinjian1.png";
|
||||
import xinjian2 from "../../assets/chat/xinjian2.png";
|
||||
import { useMessageStore } from "@/components/bs-comp/chatComponent/messageStore";
|
||||
import { formatDate } from "@/util/utils";
|
||||
import { message } from "@/components/bs-ui/toast/use-toast";
|
||||
import HomePage from "./components/ChatHome";
|
||||
|
||||
|
||||
export default function SkillChatPage() {
|
||||
@@ -70,30 +74,42 @@ export default function SkillChatPage() {
|
||||
// const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList()
|
||||
const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat, onScrollLoad } = useChatList()
|
||||
|
||||
const [location, setLocation] = useState(true)
|
||||
// select flow
|
||||
const handlerSelectFlow = async (card) => {
|
||||
// 会话ID
|
||||
const _chatId = generateUUID(32)
|
||||
// setOpen(false)
|
||||
// add list
|
||||
addChat({
|
||||
"flow_name": card.name,
|
||||
"flow_description": card.desc,
|
||||
"flow_id": card.id,
|
||||
"chat_id": _chatId,
|
||||
"create_time": "-",
|
||||
"update_time": "-",
|
||||
"flow_type": card.flow_type,
|
||||
"avatar_img": card.avatar_img,
|
||||
"avatar_color": card.avatar_color,
|
||||
})
|
||||
console.log(card)
|
||||
if (!location) {
|
||||
setLocation(true)
|
||||
return
|
||||
}
|
||||
if (card) {
|
||||
// 会话ID
|
||||
const _chatId = generateUUID(32)
|
||||
// setOpen(false)
|
||||
// add list
|
||||
addChat({
|
||||
"flow_name": card.name,
|
||||
"flow_description": card.desc,
|
||||
"flow_id": card.id,
|
||||
"chat_id": _chatId,
|
||||
"create_time": "-",
|
||||
"update_time": "-",
|
||||
"flow_type": card.flow_type,
|
||||
"avatar_img": card.avatar_img,
|
||||
"avatar_color": card.avatar_color,
|
||||
})
|
||||
setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type })
|
||||
setChatId(_chatId)
|
||||
setLocation(false)
|
||||
} else {
|
||||
return message({ title: t('prompt'), variant: 'warning', description: t('chat.pleaseSelectAnApp') })
|
||||
}
|
||||
|
||||
setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type })
|
||||
setChatId(_chatId)
|
||||
}
|
||||
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
setLocation(false)
|
||||
// console.log('chat.id :>> ', chat);
|
||||
if (chat.chat_id === chatId) return
|
||||
setSelelctChat({ id: chat.flow_id, chatId: chat.chat_id, type: chat.flow_type })
|
||||
@@ -118,13 +134,12 @@ export default function SkillChatPage() {
|
||||
return <div className="flex">
|
||||
<div className="h-screen w-[288px] relative">
|
||||
<div className="xinDuiHua absolute bg-[#000000] xinDuiHua-box">
|
||||
<SkillChatSheet onSelect={handlerSelectFlow}>
|
||||
<div id="newchat" className="xinDuiHua-btn cursor-pointer">
|
||||
{/* <PlusBoxIcon className="dark:hidden"></PlusBoxIcon> */}
|
||||
{/* <PlusBoxIconDark className="hidden dark:block"></PlusBoxIconDark> */}
|
||||
{t('chat.newChat')}
|
||||
</div>
|
||||
</SkillChatSheet>
|
||||
<div id="newchat" onClick={() => handlerSelectFlow(null)} className="xinDuiHua-btn cursor-pointer">
|
||||
{t('chat.newChat')}
|
||||
</div>
|
||||
{/* <SkillChatSheet onSelect={handlerSelectFlow}>
|
||||
|
||||
</SkillChatSheet> */}
|
||||
{/* <div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div> */}
|
||||
{/* <div className="xinDuiHua-del cursor-pointer">
|
||||
<img src={duihuaDel} alt=""/>
|
||||
@@ -146,6 +161,7 @@ export default function SkillChatPage() {
|
||||
<p className="text-[12px] text-[#ccc]">{chat.flow_name}</p>
|
||||
{/* <div>离线</div> */}
|
||||
<p className="text-[9px] text-[#666] mt-[6px] w-[95%]">{chat.latest_message?.message || ''}</p>
|
||||
{/* {!location && <p className="text-[9px] text-[#666] mt-[6px] w-[95%]">{chat.latest_message?.message || ''}</p>} */}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -163,19 +179,34 @@ export default function SkillChatPage() {
|
||||
</div>
|
||||
{/* chat */}
|
||||
{/* <ChatPanne data={selectChat}></ChatPanne> */}
|
||||
<ChatPanne appendHistory data={selectChat}></ChatPanne>
|
||||
{/* {
|
||||
{/* <ChatPanne appendHistory data={selectChat}></ChatPanne> */}
|
||||
{/* {location ? <div className="flex items-center justify-center chat-box xinDuiHua-box overflow-hidden" style={{width:"calc(100% - 288px)",flexDirection:"column"}}>
|
||||
<img src={xinjian2} alt="" className="w-[163px]" />
|
||||
<p className="mt-[50px] text-[#666666]">选择一个 NPC 或 能力,开始对话吧 ~</p>
|
||||
<div
|
||||
className="w-[190px] h-[34px] bg-[#E5BB2E] text-[#333] leading-[38px] flex items-center justify-center cursor-pointer hover:bg-[#E5BB2E] mt-[70px] text-[14px]"
|
||||
style={{borderRadius:"17px"}}
|
||||
onClick={() => {
|
||||
document.getElementById('newchat')?.click()
|
||||
}}>
|
||||
<img src={xinjian1} alt="" className="w-[16px] mr-[10px]" />
|
||||
<span>{t('chat.newChat')}</span>
|
||||
</div>
|
||||
</div>
|
||||
: <ChatPanne appendHistory data={selectChat}></ChatPanne>
|
||||
} */}
|
||||
{
|
||||
location
|
||||
? <HomePage onSelect={handlerSelectFlow}></HomePage>
|
||||
: <ChatPanne appendHistory data={selectChat}></ChatPanne>
|
||||
} */}
|
||||
}
|
||||
{/* 选择对话技能 */}
|
||||
<SkillTemps
|
||||
{/* <SkillTemps
|
||||
flows={onlineFlows}
|
||||
title={t('chat.skillTempsTitle')}
|
||||
desc={t('chat.skillTempsDesc')}
|
||||
open={open} setOpen={setOpen}
|
||||
onSelect={handlerSelectFlow}></SkillTemps>
|
||||
onSelect={handlerSelectFlow}></SkillTemps> */}
|
||||
</div>
|
||||
};
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@ export default function chatAssitantShare() {
|
||||
const wsUrl = `/api/v2/assistant/chat/${assitId}`
|
||||
|
||||
const [data] = useState<any>({ id: assitId, chatId: generateUUID(32), type: 'assistant' })
|
||||
|
||||
if (!assitId) return <div>请选择会话</div>
|
||||
|
||||
// return <div className="chatShare">
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function FilesPage() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [title, setTitle] = useState('')
|
||||
|
||||
const { page, pageSize, data: datalist, total, loading, setPage, search, reload, filterData, refreshData,loadData } = useTable({}, (param) =>
|
||||
const { page, pageSize, data: datalist, total, setPage, search, reload, filterData, refreshData,loadData } = useTable({}, (param) =>
|
||||
readFileByLibDatabase({ ...param, id, name: param.keyword }).then(res => {
|
||||
setHasPermission(res.writeable)
|
||||
return res
|
||||
@@ -123,13 +123,15 @@ export default function FilesPage() {
|
||||
}
|
||||
|
||||
return <div className="w-full h-screen p-6 relative overflow-y-auto">
|
||||
{loading && <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-[rgba(255,255,255,0.6)] dark:bg-blur-shared">
|
||||
{/* {loading && <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-[rgba(255,255,255,0.6)] dark:bg-blur-shared">
|
||||
<span className="loading loading-infinity loading-lg"></span>
|
||||
</div>}
|
||||
</div>} */}
|
||||
<ShadTooltip content="back" side="top">
|
||||
<button className="extra-side-bar-buttons w-[36px] absolute top-[26px]" onClick={() => { }} >
|
||||
<Link to='/filelib'><ArrowLeft className="side-bar-button-size" /></Link>
|
||||
</button>
|
||||
<Link to='/filelib'>
|
||||
<button className="extra-side-bar-buttons w-[36px] absolute top-[26px]" onClick={() => { }} >
|
||||
<ArrowLeft className="side-bar-button-size" />
|
||||
</button>
|
||||
</Link>
|
||||
</ShadTooltip>
|
||||
<Tabs defaultValue="account" className="w-full">
|
||||
{/* <TabsList className="ml-12">
|
||||
@@ -138,9 +140,9 @@ export default function FilesPage() {
|
||||
</TabsList> */}
|
||||
<div className="flex justify-between">
|
||||
<p className="text-[16px] ml-[40px]" style={{color:"#FFF"}}>{t('lib.fileData')}</p>
|
||||
<div className="flex justify-center items-center w-[74px] h-[27px] cursor-pointer" style={{background: "#FFD025",borderRadius: "7px"}}>
|
||||
<div className="flex justify-center items-center w-[74px] h-[27px] cursor-pointer" onClick={() => setOpen(true)} style={{background: "#FFD025",borderRadius: "7px"}}>
|
||||
{/* <img src={jia1} className="w-[14px] mr-[5px]" alt=""/> */}
|
||||
<span className="text-[12px]" style={{color:"#333333"}} onClick={() => setOpen(true)}>上 传</span>
|
||||
<span className="text-[12px]" style={{color:"#333333"}}>上 传</span>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="account">
|
||||
@@ -225,11 +227,11 @@ export default function FilesPage() {
|
||||
<UploadModal id={id} accept={appConfig.libAccepts} open={open} setOpen={handleOpen} onResult={handleUploadResult}></UploadModal>
|
||||
{/* 重复文件提醒 */}
|
||||
<dialog className={`modal ${repeatFiles.length && 'modal-open'}`}>
|
||||
<div className="modal-box w-[560px] bg-[#fff] shadow-lg dark:bg-background">
|
||||
<h3 className="font-bold text-lg relative">文件重复提示
|
||||
<X className="absolute right-0 top-0 text-gray-400 cursor-pointer" size={20} onClick={() => setRepeatFiles([])}></X>
|
||||
<div className="modal-box w-[560px] bg-[#262626] shadow-lg">
|
||||
<h3 className="font-bold text-lg relative text-[#fff]">文件重复提示
|
||||
<X className="absolute right-0 top-0 text-[#fff] cursor-pointer" size={20} onClick={() => setRepeatFiles([])}></X>
|
||||
</h3>
|
||||
<p className="py-4">以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?</p>
|
||||
<p className="py-4 text-[#fff]">以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?</p>
|
||||
<ul className="overflow-y-auto max-h-[400px]">
|
||||
{repeatFiles.map(el => (
|
||||
<li key={el.id} className="py-2 text-red-500">{el.remark}</li>
|
||||
@@ -237,7 +239,7 @@ export default function FilesPage() {
|
||||
</ul>
|
||||
<div className="modal-action">
|
||||
<Button className="h-8 rounded-full" variant="outline" onClick={() => setRepeatFiles([])}>不覆盖,保留原文件</Button>
|
||||
<Button className="h-8 rounded-full" disabled={retryLoad} onClick={() => handleRetry(repeatFiles)}>
|
||||
<Button className="h-8 rounded-full bg-[#FFD025] hover:bg-[#FFD025]" disabled={retryLoad} onClick={() => handleRetry(repeatFiles)}>
|
||||
{retryLoad && <span className="loading loading-spinner loading-xs"></span>}覆盖
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -36,8 +36,8 @@ export function transformEvent(event: string): string {
|
||||
export function transformObjectType(object: string): string {
|
||||
switch(object) {
|
||||
case 'none': return '无'
|
||||
case 'flow': return '技能'
|
||||
case 'assistant': return '助手'
|
||||
case 'flow': return '能力'
|
||||
case 'assistant': return 'NPC'
|
||||
case 'knowledge': return '知识库'
|
||||
case 'file': return '文件'
|
||||
case 'user_conf': return '用户配置'
|
||||
|
||||
@@ -23,7 +23,7 @@ import sousuo from "../../assets/npc/sousuo.png";
|
||||
import del from "../../assets/npc/del.png";
|
||||
import zidingyi1 from "../../assets/npc/zidingyi1.png";
|
||||
import zidingyijia from "../../assets/npc/zidingyijia.png";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { updataOnlineState } from "@/controllers/API/flow";
|
||||
@@ -32,17 +32,35 @@ import { userContext } from "@/contexts/userContext";
|
||||
import ShareLink from "./externalUse/shareLink";
|
||||
import npcIcon from "../../assets/npc/npcIcon.png";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import LabelShow from "@/components/bs-comp/cardComponent/LabelShow";
|
||||
import { SkillIcon } from "@/components/bs-icons/skill";
|
||||
import { getAllLabelsApi } from "@/controllers/API/label";
|
||||
|
||||
export default function Assistants() {
|
||||
const { t } = useTranslation()
|
||||
const { user } = useContext(userContext);
|
||||
const navigate = useNavigate()
|
||||
const { message } = useToast()
|
||||
const [labels, setLabels] = useState<any[]>([])
|
||||
const labelsRef = useRef([])
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isShareLink, setIsShareLink] = useState(false)
|
||||
const [isShareLinkData, setIsShareLinkData] = useState("Any");
|
||||
|
||||
const AllLabelsApi = () =>{
|
||||
getAllLabelsApi().then(res => {
|
||||
const newData = res.data.map(d => ({ label: d.name, value: d.id, edit: false, selected: false }))
|
||||
const topData = { label: t('all'), value: -1, edit: false, selected: false }
|
||||
labelsRef.current = [topData, ...newData]
|
||||
setLabels(labelsRef.current)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
AllLabelsApi();
|
||||
}, [])
|
||||
|
||||
const { page, pageSize, data: dataSource, total, loading, setPage, search, reload, refreshData } = useTable<AssistantItemDB>({ pageSize: 15 }, (param) =>
|
||||
getAssistantsApi(param.page, param.pageSize, param.keyword)
|
||||
getAssistantsApi(param.page, param.pageSize, param.keyword, param.tag_id)
|
||||
)
|
||||
|
||||
if(dataSource == "" || dataSource[0].type != 0){
|
||||
@@ -106,11 +124,25 @@ export default function Assistants() {
|
||||
// }))
|
||||
return captureAndAlertRequestErrorHoc(changeAssistantStatusApi(item.id, bln ? 1 : 0)).then(res => {
|
||||
if (res === null) {
|
||||
reload();
|
||||
refreshData((item1) => item1.id === item.id, { status: bln ? 1 : 0 })
|
||||
}
|
||||
return res
|
||||
})
|
||||
};
|
||||
|
||||
const isOperator = useMemo(() => {
|
||||
if(item && user) {
|
||||
if(user.role === 'admin') return true
|
||||
if(item.group_ids == undefined) return true
|
||||
item.group_ids.forEach(element => {
|
||||
if(user.admin_groups.includes(element)) return true
|
||||
})
|
||||
if(item.user_id === user.user_id) return true
|
||||
}
|
||||
return false
|
||||
},[item, user])
|
||||
|
||||
return(
|
||||
<Flexbox align={'flex-start'}>
|
||||
{item.type == 0 ?
|
||||
@@ -156,6 +188,15 @@ export default function Assistants() {
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.desc}</p>
|
||||
<div>
|
||||
<LabelShow show={item.tags.length > 0} isOperator={isOperator}
|
||||
resource={{id:item.id, type:"assist"}}
|
||||
labels={item.tags.map(d => ({label:d.name, value:d.id, selected:true, edit:false}))}
|
||||
reload={reload}
|
||||
AllLabelsApi={AllLabelsApi}
|
||||
all={labels.filter(a => a.value !== -1)}>
|
||||
</LabelShow>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => item.status !== 1 && navigate('/assistant/' + item.id,{state: { value:inputRef.current.value, page:page}})}>
|
||||
@@ -170,6 +211,9 @@ export default function Assistants() {
|
||||
{item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>}
|
||||
<div>
|
||||
<span className="text-[9px] text-[#666666] ml-[14px]">{item.user_name}创建</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Switch checked={openSwitch} onCheckedChange={handleChange} /> */}
|
||||
<Switch checked={openSwitch} onClick={() => !item.write && item.status !== 1 && message({ title: t('prompt'), description: t('skills.contactAdmin'), variant: 'warning' })} onCheckedChange={item.write && handleChange} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SkillTempSheet from "@/components/bs-comp/sheets/SkillTempSheet";
|
||||
import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import CardComponent, { TitleIconBg } from "../../components/bs-comp/cardComponent";
|
||||
@@ -37,6 +37,8 @@ import Templates from "./temps";
|
||||
import { Size } from "recharts/types/util/types";
|
||||
import SkillTemps from "./components/SkillTemps";
|
||||
import nengliIcon from "../../assets/npc/nengliIcon.png";
|
||||
import LabelShow from "@/components/bs-comp/cardComponent/LabelShow";
|
||||
import { getAllLabelsApi } from "@/controllers/API/label";
|
||||
|
||||
export default function Skills() {
|
||||
const { t } = useTranslation()
|
||||
@@ -45,7 +47,7 @@ export default function Skills() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { page, pageSize, data: dataSource, total, loading, setPage, search, reload, refreshData } = useTable<FlowType>({ pageSize: 14 }, (param) =>
|
||||
readFlowsFromDatabase(param.page, param.pageSize, param.keyword)
|
||||
readFlowsFromDatabase(param.page, param.pageSize, param.keyword, param.tag_id)
|
||||
)
|
||||
|
||||
if(dataSource == "" || dataSource[0].type != 0){
|
||||
@@ -118,6 +120,22 @@ export default function Skills() {
|
||||
}))
|
||||
}
|
||||
|
||||
const [labels, setLabels] = useState<any[]>([])
|
||||
const [selectLabel, setSelectLabel] = useState({ label: '', value: null })
|
||||
const labelsRef = useRef([])
|
||||
|
||||
const AllLabelsApi = () =>{
|
||||
getAllLabelsApi().then(res => {
|
||||
const newData = res.data.map(d => ({ label: d.name, value: d.id, edit: false, selected: false }))
|
||||
const topData = { label: t('all'), value: -1, edit: false, selected: false }
|
||||
labelsRef.current = [topData, ...newData]
|
||||
setLabels(labelsRef.current)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
AllLabelsApi();
|
||||
}, [])
|
||||
|
||||
const nvaigate = useNavigate()
|
||||
const handleEdit = (id,pageNo) => {
|
||||
// onBeforeEdit?.()
|
||||
@@ -134,15 +152,33 @@ export default function Skills() {
|
||||
const [openSwitch, setOpenSwitch] = useState(item.status === 2);
|
||||
const handleChange = (bln) => {
|
||||
captureAndAlertRequestErrorHoc(updataOnlineState(item.id, item, bln).then(res => {
|
||||
setOpenSwitch(bln);
|
||||
// setOpenSwitch(bln);
|
||||
if (res) {
|
||||
reload();
|
||||
refreshData((item1) => item1.id === item.id, { status: bln ? 2 : 1 })
|
||||
}
|
||||
return res
|
||||
// loadPage(page.pageNo);
|
||||
// itema.status = bln ? 2 : 1
|
||||
}))
|
||||
};
|
||||
|
||||
const isOperator = useMemo(() => {
|
||||
if(item && user) {
|
||||
// if(user.role === 'admin') {
|
||||
// item.group_ids.forEach(element => {
|
||||
// if(user.admin_groups.includes(element)) return true
|
||||
// })
|
||||
// }
|
||||
if(user.role === 'admin') return true
|
||||
if(item.group_ids){
|
||||
item.group_ids.forEach(element => {
|
||||
if(user.admin_groups.includes(element)) return true
|
||||
})
|
||||
}
|
||||
if(item.user_id === user.user_id) return true
|
||||
}
|
||||
return false
|
||||
},[item, user])
|
||||
|
||||
return(
|
||||
<Flexbox align={'flex-start'}>
|
||||
{item.type == 0 ?
|
||||
@@ -172,6 +208,15 @@ export default function Skills() {
|
||||
<p className="ml-[14px] yichu">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.description}</p>
|
||||
<div>
|
||||
<LabelShow show={item.tags.length > 0} isOperator={isOperator}
|
||||
resource={{id:item.id, type:"skill"}}
|
||||
labels={item.tags.map(d => ({label:d.name, value:d.id, selected:true, edit:false}))}
|
||||
reload={reload}
|
||||
AllLabelsApi={AllLabelsApi}
|
||||
all={labels.filter(a => a.value !== -1)}>
|
||||
</LabelShow>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => handleSetting(item)}>
|
||||
@@ -190,6 +235,10 @@ export default function Skills() {
|
||||
{item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>}
|
||||
|
||||
<div>
|
||||
<span className="text-[9px] text-[#666666] ml-[14px]">{item.user_name}创建</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Switch checked={openSwitch} onCheckedChange={handleChange} /> */}
|
||||
<Switch checked={openSwitch} onClick={() => !item.write && item.status !== 2 && message({ title: t('prompt'), description: "请联系管理员上线能力", variant: 'warning' })} onCheckedChange={item.write && handleChange} />
|
||||
|
||||
@@ -2712,7 +2712,7 @@
|
||||
// justify-content:space-between;
|
||||
/* padding: 14px; */
|
||||
// position: relative;
|
||||
padding-bottom: 55px;
|
||||
padding-bottom: 50px;
|
||||
>div:nth-of-type(2){
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2733,6 +2733,33 @@
|
||||
margin-top: 8px;
|
||||
}
|
||||
>div:nth-of-type(3){
|
||||
|
||||
}
|
||||
.biaoqian{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 14px;
|
||||
margin-top: 14px;
|
||||
// position: absolute;
|
||||
// bottom: 55px;
|
||||
>div{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
>div{
|
||||
padding: 2px 6px;
|
||||
// height: 16px;
|
||||
margin-right: 7px;
|
||||
background: rgba(255, 213, 76, 0.15);
|
||||
border-radius: 8px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 9px;
|
||||
color: #CCA31B;
|
||||
}
|
||||
}
|
||||
}
|
||||
>div:nth-of-type(4){
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -3845,7 +3872,16 @@
|
||||
height: 15px!important;
|
||||
border: 1px solid #ffd025!important;
|
||||
}
|
||||
|
||||
.biaoqianTab .data-\[state\=checked\]\:bg-primary[data-state=checked]{
|
||||
width: 15px!important;
|
||||
height: 15px!important;
|
||||
background: #ffd025!important;
|
||||
}
|
||||
.biaoqianTab button{
|
||||
width: 15px!important;
|
||||
height: 15px!important;
|
||||
border: 1px solid #ffd025!important;
|
||||
}
|
||||
|
||||
.build-tab{
|
||||
display: flex;
|
||||
@@ -4009,6 +4045,24 @@
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
.npcInput2{
|
||||
background: #0F0F0F!important;
|
||||
border-radius: 17px!important;
|
||||
outline: none!important;
|
||||
--tw-ring-color:none!important;
|
||||
border: none!important;
|
||||
--tw-ring-offset-color:none!important;
|
||||
color: #fff!important;
|
||||
|
||||
&::-webkit-input-placeholder{
|
||||
color: #666!important;
|
||||
}
|
||||
// outline: #997e1f;
|
||||
&:focus{
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.npcInput4{
|
||||
background: #000000!important;
|
||||
@@ -4194,14 +4248,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改垂直滚动条的颜色 */
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
width: 10px; /* 设置滚动条的宽度 */
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-horizontal {
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
// background-color: #666; /* 设置滚动条的背景颜色 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
display: none;
|
||||
background-color: #333; /* 设置滚动条的颜色 */
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.SelectTrigger{
|
||||
@@ -4258,3 +4319,8 @@
|
||||
.bg-reset{
|
||||
box-shadow: 0px 3px 14px 0px rgba(255,255,255,0.2)!important;
|
||||
}
|
||||
.chatHomeInput{
|
||||
input{
|
||||
width: 100%!important;
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,8 @@ export function useTable<T extends object>(param, apiFun) {
|
||||
// 数据过滤
|
||||
filterData: (p) => {
|
||||
paramRef.current = { ...paramRef.current, ...p };
|
||||
setPage({ ...page, page: 1 });
|
||||
// setPage({ ...page, page: 1 });
|
||||
page.page === 1 ? loadData() : setPage({ ...page, page: 1 });
|
||||
},
|
||||
// 更新数据
|
||||
refreshData: (compareFn, data) => {
|
||||
|
||||
Reference in New Issue
Block a user