diff --git a/.DS_Store b/.DS_Store index f166eaf..21301a4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/build/index.html b/build/index.html index 1846e8f..f3fc146 100644 --- a/build/index.html +++ b/build/index.html @@ -1 +1 @@ -NPCs
\ No newline at end of file +NPCs
\ No newline at end of file diff --git a/build/locales/en/bs.json b/build/locales/en/bs.json index 56ad2eb..52066a4 100644 --- a/build/locales/en/bs.json +++ b/build/locales/en/bs.json @@ -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以内", diff --git a/build/locales/zh/bs.json b/build/locales/zh/bs.json index 00a08d8..735204d 100644 --- a/build/locales/zh/bs.json +++ b/build/locales/zh/bs.json @@ -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", diff --git a/public/locales/en/bs.json b/public/locales/en/bs.json index 56ad2eb..52066a4 100644 --- a/public/locales/en/bs.json +++ b/public/locales/en/bs.json @@ -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以内", diff --git a/public/locales/zh/bs.json b/public/locales/zh/bs.json index 00a08d8..735204d 100644 --- a/public/locales/zh/bs.json +++ b/public/locales/zh/bs.json @@ -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", diff --git a/src/.DS_Store b/src/.DS_Store index c128842..e9a3a46 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/assets/.DS_Store b/src/assets/.DS_Store index 7ae5f5a..ab780cc 100644 Binary files a/src/assets/.DS_Store and b/src/assets/.DS_Store differ diff --git a/src/assets/chat/.DS_Store b/src/assets/chat/.DS_Store index 413d9d3..652d365 100644 Binary files a/src/assets/chat/.DS_Store and b/src/assets/chat/.DS_Store differ diff --git a/src/assets/chat/biaoqian-paixu.png b/src/assets/chat/biaoqian-paixu.png new file mode 100644 index 0000000..3b61a9c Binary files /dev/null and b/src/assets/chat/biaoqian-paixu.png differ diff --git a/src/assets/chat/shengQue.png b/src/assets/chat/shengQue.png new file mode 100644 index 0000000..52858e0 Binary files /dev/null and b/src/assets/chat/shengQue.png differ diff --git a/src/assets/npc/.DS_Store b/src/assets/npc/.DS_Store index c423b6e..897d645 100644 Binary files a/src/assets/npc/.DS_Store and b/src/assets/npc/.DS_Store differ diff --git a/src/assets/npc/biaoqian-bian.png b/src/assets/npc/biaoqian-bian.png new file mode 100644 index 0000000..eb3613d Binary files /dev/null and b/src/assets/npc/biaoqian-bian.png differ diff --git a/src/assets/npc/biaoqian-del.png b/src/assets/npc/biaoqian-del.png new file mode 100644 index 0000000..f60b601 Binary files /dev/null and b/src/assets/npc/biaoqian-del.png differ diff --git a/src/assets/npc/tianjiabiaoqian.png b/src/assets/npc/tianjiabiaoqian.png new file mode 100644 index 0000000..71e4a3e Binary files /dev/null and b/src/assets/npc/tianjiabiaoqian.png differ diff --git a/src/components/.DS_Store b/src/components/.DS_Store index 9e5deb6..b8dd15c 100644 Binary files a/src/components/.DS_Store and b/src/components/.DS_Store differ diff --git a/src/components/bs-comp/cardComponent/LabelShow.tsx b/src/components/bs-comp/cardComponent/LabelShow.tsx new file mode 100644 index 0000000..d68fcda --- /dev/null +++ b/src/components/bs-comp/cardComponent/LabelShow.tsx @@ -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 ( +
+ {isShow ? ( + isOperator ? ( + +
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"> + {/* +
+ {freshData.map((l, index) => {l.label}{index !== freshData.length - 1 && ','})} +
*/} +
+ +
+ {freshData.map((l, index) =>
{l.label}
)} + {/*
知识库
*/} + {/*
法律
*/} +
+
+
+
+ ) : ( +
+ {/* +
+ {freshData.map((l, index) => {l.label}{index !== freshData.length - 1 && ','})} +
*/} +
+ {/* */} +
+ {freshData.map((l, index) =>
{l.label}
)} + {/*
知识库
+
法律
*/} +
+
+
+ ) + ) : ( + isOperator ? ( + +
e.stopPropagation()} className=""> + {/* +
+ {t('tag.addLabel')} +
*/} +
+ +
+
+
+ ) : ( +
+ ) + )} +
+ ) +} diff --git a/src/components/bs-comp/chatComponent/ChatInput.tsx b/src/components/bs-comp/chatComponent/ChatInput.tsx index 4956ee7..0d42375 100644 --- a/src/components/bs-comp/chatComponent/ChatInput.tsx +++ b/src/components/bs-comp/chatComponent/ChatInput.tsx @@ -270,7 +270,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on {/* form */} { formShow &&
-
+
{inputForm}
@@ -295,7 +295,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
{ form &&
(showWhenLocked || !inputLock.locked) && setFormShow(!formShow)} >
} @@ -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) { diff --git a/src/components/bs-comp/chatComponent/MessageBs.tsx b/src/components/bs-comp/chatComponent/MessageBs.tsx index 7555d31..dcea3f8 100644 --- a/src/components/bs-comp/chatComponent/MessageBs.tsx +++ b/src/components/bs-comp/chatComponent/MessageBs.tsx @@ -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 ] diff --git a/src/components/bs-comp/chatComponent/MessageUser.tsx b/src/components/bs-comp/chatComponent/MessageUser.tsx index 99cb0e8..c718888 100644 --- a/src/components/bs-comp/chatComponent/MessageUser.tsx +++ b/src/components/bs-comp/chatComponent/MessageUser.tsx @@ -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) diff --git a/src/components/bs-comp/selectComponent/LabelSelect.tsx b/src/components/bs-comp/selectComponent/LabelSelect.tsx new file mode 100644 index 0000000..460b0e7 --- /dev/null +++ b/src/components/bs-comp/selectComponent/LabelSelect.tsx @@ -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 + + {children} + + e.stopPropagation()}> +
+ { + if (e.key === 'Enter') { + (!data.length && user.role === 'admin') ? handleAdd() : null + } + }} /> +
+
+ {data.map(d =>
+
+ handleChecked(d.value)} /> + { + d.edit + ? handleChange(e, d.value)} + onKeyDown={(e) => handleSave(e, d.value)} /> + : + } +
+ {user.role === 'admin' &&
+ handleEdit(d.value)} className="w-[14px] cursor-pointer"/> + handleDelete(d)} className="w-[14px] ml-[14px] cursor-pointer"/> + {/* handleEdit(d.value)} /> */} + {/* handleDelete(d)} className="text-gray-600 cursor-pointer" /> */} +
} +
)} + {(showAdd && keyword != '' && user.role === 'admin') &&
+ 创建标签 +
} +
+
+
+} diff --git a/src/components/bs-comp/sheets/SkillChatSheet.tsx b/src/components/bs-comp/sheets/SkillChatSheet.tsx index cf14c32..3477069 100644 --- a/src/components/bs-comp/sheets/SkillChatSheet.tsx +++ b/src/components/bs-comp/sheets/SkillChatSheet.tsx @@ -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([]) + 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([]) 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 }) { 选择对话 选择一个您想使用的上线NPC或能力 {/* setKeyword(e.target.value)} /> */} - + +
+ {/* @ts-ignore */} + {user.role === 'admin' && setMarkLabelOpen(true)} />} + {/* */} +
{ setChooseId(null); loadData(false) }}>全部
+ { + labels.map((l, index) => index <= 11 && + // +
handleTagSearch(l.value)}>{l.label}
) + } + +
- + {flag && }
+ }; diff --git a/src/components/bs-icons/office/index.tsx b/src/components/bs-icons/office/index.tsx index 10e6163..60a88a8 100644 --- a/src/components/bs-icons/office/index.tsx +++ b/src/components/bs-icons/office/index.tsx @@ -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 ; }); diff --git a/src/components/bs-icons/prompt/Prompt.svg b/src/components/bs-icons/prompt/Prompt.svg new file mode 100644 index 0000000..e1442f0 --- /dev/null +++ b/src/components/bs-icons/prompt/Prompt.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/components/bs-icons/prompt/index.tsx b/src/components/bs-icons/prompt/index.tsx new file mode 100644 index 0000000..61d56b8 --- /dev/null +++ b/src/components/bs-icons/prompt/index.tsx @@ -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 ; +}); \ No newline at end of file diff --git a/src/components/bs-ui/alertDialog/index.tsx b/src/components/bs-ui/alertDialog/index.tsx index 9473388..dbd86e6 100644 --- a/src/components/bs-ui/alertDialog/index.tsx +++ b/src/components/bs-ui/alertDialog/index.tsx @@ -35,7 +35,7 @@ const AlertDialogContent = React.forwardRef< ) => (
(({ className, ...props }, ref) => ( )) @@ -90,7 +90,7 @@ const AlertDialogDescription = React.forwardRef< >(({ className, ...props }, ref) => ( )) @@ -103,7 +103,7 @@ const AlertDialogAction = React.forwardRef< >(({ className, ...props }, ref) => ( )) @@ -118,7 +118,8 @@ const AlertDialogCancel = React.forwardRef< className={cname( buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", - className + className, + "baogao-btn" )} {...props} /> diff --git a/src/components/bs-ui/input/index.tsx b/src/components/bs-ui/input/index.tsx index e5b8e43..9f9c90f 100644 --- a/src/components/bs-ui/input/index.tsx +++ b/src/components/bs-ui/input/index.tsx @@ -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 { } diff --git a/src/controllers/API/assistant.ts b/src/controllers/API/assistant.ts index b77211e..1c963c5 100644 --- a/src/controllers/API/assistant.ts +++ b/src/controllers/API/assistant.ts @@ -13,10 +13,18 @@ export interface AssistantItemDB { status: number; } // 获取助手列表 -export const getAssistantsApi = async (page, limit, name): Promise => { +// export const getAssistantsApi = async (page, limit, name): Promise => { +// return await axios.get(`/api/v1/assistant`, { +// params: { +// page, limit, name +// } +// }); +// }; +export const getAssistantsApi = async (page, limit, name, tag_id): Promise => { return await axios.get(`/api/v1/assistant`, { params: { - page, limit, name + page, limit, name, + tag_id: tag_id === -1 ? null : tag_id } }); }; diff --git a/src/controllers/API/flow.ts b/src/controllers/API/flow.ts index 9514143..75f480f 100644 --- a/src/controllers/API/flow.ts +++ b/src/controllers/API/flow.ts @@ -148,8 +148,13 @@ export async function saveFlowToDatabase(newFlow: { * @returns {Promise} 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 }; } diff --git a/src/controllers/API/label.ts b/src/controllers/API/label.ts new file mode 100644 index 0000000..aa03adb --- /dev/null +++ b/src/controllers/API/label.ts @@ -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 + }) +} \ No newline at end of file diff --git a/src/layout/MainLayout.tsx b/src/layout/MainLayout.tsx index 0ce29fc..e9a0b79 100755 --- a/src/layout/MainLayout.tsx +++ b/src/layout/MainLayout.tsx @@ -99,7 +99,7 @@ export default function MainLayout() { } { - isMenu('filelib') && + isMenu('knowledge') && {/* */} {t('menu.knowledge')} diff --git a/src/pages/ChatAppPage/components/ChatHome.tsx b/src/pages/ChatAppPage/components/ChatHome.tsx new file mode 100644 index 0000000..86c56ed --- /dev/null +++ b/src/pages/ChatAppPage/components/ChatHome.tsx @@ -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([]) + 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) => ( + { onSelect(item); setOpen(false) }}> +
+ + +
+ +
+
+
+
+
+ +
+

{item.name}

+
+ +
+
+
+

{item.desc}

+
+ {item.flow_type === 'flow' ? '能力' : 'NPC'} +
+
+ ); + + return
+
e.stopPropagation()}> + {/*
+
*/} +
+ {/* 选择对话 + 选择一个您想使用的上线NPC或能力 */} + {/* setKeyword(e.target.value)} /> */} +

选择对话

+

选择一个您想使用的上线NPC或能力

+
+ +
+
+
+ {/* @ts-ignore */} + {user.role === 'admin' && setMarkLabelOpen(true)} />} + {/* */} +
{ setChooseId(null); loadData(false) }}>全部
+ { + labels.map((l, index) => index <= 11 && +
handleTagSearch(l.value)}>{l.label}
) + } +
+
+
+ {options.length ? + :
+ +

未找到您搜索的「NPC」或「能力」

+
} + {flag && } +
+ +
+
+} \ No newline at end of file diff --git a/src/pages/ChatAppPage/components/ChatPanne.tsx b/src/pages/ChatAppPage/components/ChatPanne.tsx index d2e5423..35fd636 100644 --- a/src/pages/ChatAppPage/components/ChatPanne.tsx +++ b/src/pages/ChatAppPage/components/ChatPanne.tsx @@ -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) // // 停止状态 diff --git a/src/pages/ChatAppPage/components/MarkLabel.tsx b/src/pages/ChatAppPage/components/MarkLabel.tsx new file mode 100644 index 0000000..290ae58 --- /dev/null +++ b/src/pages/ChatAppPage/components/MarkLabel.tsx @@ -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
+ {e.stopPropagation(); onCancel(data.id)}} + className='text-[#999] absolute top-[-6px] right-[-6px] cursor-pointer'/> +
+ {data.index} +
+
+ {children} +
+
+} + +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 + + + + + {t('chat.operationTips')} + + +
+
+
+ {/*
*/} + { + labels.map(l => + + //
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} + //
+ ) + } +
+
+
+
+ {t('chat.selected')}:{selected.length}/10 + setFlag(true)} onDragUpdate={() => setFlag(true)}> + + {(provided) => ( +
+ {selected.map((b,index) => ( + + {(provided) => ( +
+ + {b.label} + +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+
+
+
+ + + + + +
+} \ No newline at end of file diff --git a/src/pages/ChatAppPage/components/ResouceModal.tsx b/src/pages/ChatAppPage/components/ResouceModal.tsx index a4c302a..8099668 100644 --- a/src/pages/ChatAppPage/components/ResouceModal.tsx +++ b/src/pages/ChatAppPage/components/ResouceModal.tsx @@ -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, '$1')) - onInit(res) - }) + const loadData = () => { + splitWordApi(msg, id).then((res) => { + // 匹配 + const reg = new RegExp(`(${res.join('|')})`, 'g') + setHtml(msg.replace(reg, '$1')) + 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
+ return

} // 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
+ return
+ { + isMobile &&
+ {!collapse && { setCollapse(true) }} className="">收起} + {collapse && { setCollapse(false) }} className="">展开} +
+ } + { + isMobile &&
+ 关闭 +
+ } {/* left */} -
- {/* label */} -
- {t('chat.filterLabel')} -
?
-
-
- {words.map((str, i) =>
{str} onClose(i)}>x
)} - {/* 自定义 */} - { - editCustomKey ?
{ - if (event.key === "Enter" && !event.shiftKey) { + { + (!isMobile || !collapse) &&
+ {/* label */} +
+
+ {t('chat.filterLabel')} + + + + + + +

{t('chat.tooltipText')}

+
+
+
+
+
+
+ {words.map((str, i) =>
{str} onClose(i)}>x
)} + { + editCustomKey ?
{ + if (event.key === "Enter" && !event.shiftKey) { + handleAddKeyword(inputRef.current.value); + } + }} + onBlur={() => { handleAddKeyword(inputRef.current.value); - } - }} - onBlur={() => { - handleAddKeyword(inputRef.current.value); - }}>
: -
{t('chat.addCustomLabel')}
- } -
- {/* files */} -
-

{t('chat.sourceDocumentsLabel')}

- {files.map(_file => - _file.right ?
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'}`}> -

{_file.fileName}

- - {_file.score} -
: -
-

是真的马赛克.msk

+ }}>
: +
{t('chat.addCustomLabel')}
+ } +
+ {/* files */} +
+

{t('chat.sourceDocumentsLabel')}

+ {files.map(_file => + _file.right ?
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'}`}> +

{_file.fileName}

+ {_file.score} -
- )} - {!files.length &&

{t('chat.noMatchedFilesMessage')}

} +
: +
+

是真的马赛克.msk

+ {_file.score} +
+ )} + {!files.length &&

{t('chat.noMatchedFilesMessage')}

} +
-
+ } {/* file pane */} {file && children(file)}
} -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({}) + useImperativeHandle(ref, () => ({ + openModal: (data) => { + setOpen(true) + setData(data) + } + })); - // return () => localStorage.setItem(KEYWORDS_LOCAL_KEY, JSON.stringify(keywords)) - // }, []) const MemoizedFileView = React.memo(FileView); - return setOpen(false)}> -
e.stopPropagation()}> + return + + {/* + {t('chat.feedback')} + */} {open &&
- - + + setOpen(false)}> { (file) => file.fileUrl ? : -
+

{t('chat.fileStorageFailure')}

}
} -
-
-}; + +
+}); -// const useRefState = (state) => { -// const [data, setData] = useState(state) -// const ref = useRef(state) -// return [data, ref, (nState) => { -// setData(nState) -// ref.current = nState -// }] -// } +export default ResouceModal diff --git a/src/pages/ChatAppPage/index.tsx b/src/pages/ChatAppPage/index.tsx index c1f3618..166f579 100755 --- a/src/pages/ChatAppPage/index.tsx +++ b/src/pages/ChatAppPage/index.tsx @@ -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, - }) - - setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type }) - setChatId(_chatId) + 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') }) + } + } // 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
- -
- {/* */} - {/* */} - {t('chat.newChat')} -
-
+
handlerSelectFlow(null)} className="xinDuiHua-btn cursor-pointer"> + {t('chat.newChat')} +
+ {/* + + */} {/*
setOpen(true)}>{t('chat.newChat')}
*/} {/*
@@ -146,6 +161,7 @@ export default function SkillChatPage() {

{chat.flow_name}

{/*
离线
*/}

{chat.latest_message?.message || ''}

+ {/* {!location &&

{chat.latest_message?.message || ''}

} */}
@@ -163,19 +179,34 @@ export default function SkillChatPage() {
{/* chat */} {/* */} - - {/* { + {/* */} + {/* {location ?
+ +

选择一个 NPC 或 能力,开始对话吧 ~

+
{ + document.getElementById('newchat')?.click() + }}> + + {t('chat.newChat')} +
+
+ : + } */} + { location ? : - } */} + } {/* 选择对话技能 */} - + onSelect={handlerSelectFlow}> */}
}; /** diff --git a/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx b/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx index c17e4c8..67d6e8a 100644 --- a/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx +++ b/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx @@ -11,7 +11,6 @@ export default function chatAssitantShare() { const wsUrl = `/api/v2/assistant/chat/${assitId}` const [data] = useState({ id: assitId, chatId: generateUUID(32), type: 'assistant' }) - if (!assitId) return
请选择会话
// return
diff --git a/src/pages/FileLibPage/files.tsx b/src/pages/FileLibPage/files.tsx index c4d55c3..7482f28 100755 --- a/src/pages/FileLibPage/files.tsx +++ b/src/pages/FileLibPage/files.tsx @@ -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
- {loading &&
+ {/* {loading &&
-
} +
} */} - + + + {/* @@ -138,9 +140,9 @@ export default function FilesPage() { */}

{t('lib.fileData')}

-
+
setOpen(true)} style={{background: "#FFD025",borderRadius: "7px"}}> {/* */} - setOpen(true)}>上 传 + 上 传
@@ -225,11 +227,11 @@ export default function FilesPage() { {/* 重复文件提醒 */} -
-

文件重复提示 - setRepeatFiles([])}> +
+

文件重复提示 + setRepeatFiles([])}>

-

以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?

+

以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?

    {repeatFiles.map(el => (
  • {el.remark}
  • @@ -237,7 +239,7 @@ export default function FilesPage() {
-
diff --git a/src/pages/LogPage/utils/index.ts b/src/pages/LogPage/utils/index.ts index 32c511d..c55978a 100644 --- a/src/pages/LogPage/utils/index.ts +++ b/src/pages/LogPage/utils/index.ts @@ -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 '用户配置' diff --git a/src/pages/SkillPage/tabAssistant.tsx b/src/pages/SkillPage/tabAssistant.tsx index 68a17e3..202c1aa 100644 --- a/src/pages/SkillPage/tabAssistant.tsx +++ b/src/pages/SkillPage/tabAssistant.tsx @@ -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([]) + 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({ 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( {item.type == 0 ? @@ -156,6 +188,15 @@ export default function Assistants() {

{item.name}

{item.desc}

+
+ 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)}> + +
{!openSwitch &&
item.status !== 1 && navigate('/assistant/' + item.id,{state: { value:inputRef.current.value, page:page}})}> @@ -170,6 +211,9 @@ export default function Assistants() { {item.write &&
delConfirm(item)}>
} +
+ {item.user_name}创建 +
{/* */} !item.write && item.status !== 1 && message({ title: t('prompt'), description: t('skills.contactAdmin'), variant: 'warning' })} onCheckedChange={item.write && handleChange} /> diff --git a/src/pages/SkillPage/tabSkills.tsx b/src/pages/SkillPage/tabSkills.tsx index d06c765..90850f0 100644 --- a/src/pages/SkillPage/tabSkills.tsx +++ b/src/pages/SkillPage/tabSkills.tsx @@ -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({ 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([]) + 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( {item.type == 0 ? @@ -172,6 +208,15 @@ export default function Skills() {

{item.name}

{item.description}

+
+ 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)}> + +
{!openSwitch &&
handleSetting(item)}> @@ -190,6 +235,10 @@ export default function Skills() { {item.write &&
delConfirm(item)}>
} + +
+ {item.user_name}创建 +
{/* */} !item.write && item.status !== 2 && message({ title: t('prompt'), description: "请联系管理员上线能力", variant: 'warning' })} onCheckedChange={item.write && handleChange} /> diff --git a/src/style/zk.scss b/src/style/zk.scss index 5661490..94250ce 100644 --- a/src/style/zk.scss +++ b/src/style/zk.scss @@ -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,16 +4248,23 @@ } } } + + /* 修改垂直滚动条的颜色 */ ::-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{ background: rgba(255, 255, 255, 0.05); border-radius: 14px!important; @@ -4257,4 +4318,9 @@ } .bg-reset{ box-shadow: 0px 3px 14px 0px rgba(255,255,255,0.2)!important; + } + .chatHomeInput{ + input{ + width: 100%!important; + } } \ No newline at end of file diff --git a/src/util/hook.ts b/src/util/hook.ts index 7d13f96..b7d1f07 100644 --- a/src/util/hook.ts +++ b/src/util/hook.ts @@ -109,7 +109,8 @@ export function useTable(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) => {