zhangkai hace 1 año
padre
commit
94be64311c
Se han modificado 43 ficheros con 1256 adiciones y 201 borrados
  1. BIN
      .DS_Store
  2. 1 1
      build/index.html
  3. 5 5
      build/locales/en/bs.json
  4. 26 1
      build/locales/zh/bs.json
  5. 5 5
      public/locales/en/bs.json
  6. 26 1
      public/locales/zh/bs.json
  7. BIN
      src/.DS_Store
  8. BIN
      src/assets/.DS_Store
  9. BIN
      src/assets/chat/.DS_Store
  10. BIN
      src/assets/chat/biaoqian-paixu.png
  11. BIN
      src/assets/chat/shengQue.png
  12. BIN
      src/assets/npc/.DS_Store
  13. BIN
      src/assets/npc/biaoqian-bian.png
  14. BIN
      src/assets/npc/biaoqian-del.png
  15. BIN
      src/assets/npc/tianjiabiaoqian.png
  16. BIN
      src/components/.DS_Store
  17. 110 0
      src/components/bs-comp/cardComponent/LabelShow.tsx
  18. 3 3
      src/components/bs-comp/chatComponent/ChatInput.tsx
  19. 1 0
      src/components/bs-comp/chatComponent/MessageBs.tsx
  20. 2 1
      src/components/bs-comp/chatComponent/MessageUser.tsx
  21. 200 0
      src/components/bs-comp/selectComponent/LabelSelect.tsx
  22. 75 23
      src/components/bs-comp/sheets/SkillChatSheet.tsx
  23. 1 1
      src/components/bs-icons/office/index.tsx
  24. 2 0
      src/components/bs-icons/prompt/Prompt.svg
  25. 10 0
      src/components/bs-icons/prompt/index.tsx
  26. 7 6
      src/components/bs-ui/alertDialog/index.tsx
  27. 1 1
      src/components/bs-ui/input/index.tsx
  28. 10 2
      src/controllers/API/assistant.ts
  29. 7 2
      src/controllers/API/flow.ts
  30. 61 0
      src/controllers/API/label.ts
  31. 1 1
      src/layout/MainLayout.tsx
  32. 158 0
      src/pages/ChatAppPage/components/ChatHome.tsx
  33. 1 0
      src/pages/ChatAppPage/components/ChatPanne.tsx
  34. 146 0
      src/pages/ChatAppPage/components/MarkLabel.tsx
  35. 145 88
      src/pages/ChatAppPage/components/ResouceModal.tsx
  36. 61 30
      src/pages/ChatAppPage/index.tsx
  37. 0 1
      src/pages/ChatAppPage/mobile/chatAssitantShare.tsx
  38. 15 13
      src/pages/FileLibPage/files.tsx
  39. 2 2
      src/pages/LogPage/utils/index.ts
  40. 46 2
      src/pages/SkillPage/tabAssistant.tsx
  41. 54 5
      src/pages/SkillPage/tabSkills.tsx
  42. 72 6
      src/style/zk.scss
  43. 2 1
      src/util/hook.ts

BIN
.DS_Store


+ 1 - 1
build/index.html

@@ -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>

+ 5 - 5
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以内",

+ 26 - 1
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",

+ 5 - 5
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以内",

+ 26 - 1
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",

BIN
src/.DS_Store


BIN
src/assets/.DS_Store


BIN
src/assets/chat/.DS_Store


BIN
src/assets/chat/biaoqian-paixu.png


BIN
src/assets/chat/shengQue.png


BIN
src/assets/npc/.DS_Store


BIN
src/assets/npc/biaoqian-bian.png


BIN
src/assets/npc/biaoqian-del.png


BIN
src/assets/npc/tianjiabiaoqian.png


BIN
src/components/.DS_Store


+ 110 - 0
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 (
+    <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>
+  )
+}

+ 3 - 3
src/components/bs-comp/chatComponent/ChatInput.tsx

@@ -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) {

+ 1 - 0
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
     ]

+ 2 - 1
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)

+ 200 - 0
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 <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>
+}

+ 75 - 23
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<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>
 };

+ 1 - 1
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 <Word ref={ref} {...props} className={_className} />;
 });

+ 2 - 0
src/components/bs-icons/prompt/Prompt.svg

@@ -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>

+ 10 - 0
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 <Prompt ref={ref} {...props} className={_className} />;
+});

+ 7 - 6
src/components/bs-ui/alertDialog/index.tsx

@@ -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}
     />

+ 1 - 1
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<HTMLInputElement> { }
 

+ 10 - 2
src/controllers/API/assistant.ts

@@ -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
         }
     });
 };

+ 7 - 2
src/controllers/API/flow.ts

@@ -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 - 0
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
+    })
+}

+ 1 - 1
src/layout/MainLayout.tsx

@@ -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 - 0
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<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>
+}

+ 1 - 0
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)
     // // 停止状态

+ 146 - 0
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 <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>
+}

+ 145 - 88
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, '<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)' }}>
-        {/* 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>
+    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>
-            <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) {
-                                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>
-                }
+        }
+        {
+            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>
-            {/* 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>
+        }
+        {/* left */}
+        {
+            (!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);
+                            }}></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>
-};
-
-// const useRefState = (state) => {
-//     const [data, setData] = useState(state)
-//     const ref = useRef(state)
-//     return [data, ref, (nState) => {
-//         setData(nState)
-//         ref.current = nState
-//     }]
-// }
+        </DialogContent>
+    </Dialog>
+});
+
+export default ResouceModal

+ 61 - 30
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 <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>
 };
 /**

+ 0 - 1
src/pages/ChatAppPage/mobile/chatAssitantShare.tsx

@@ -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">

+ 15 - 13
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 <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>

+ 2 - 2
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 '用户配置'

+ 46 - 2
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<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 ?
@@ -157,6 +189,15 @@ export default function Assistants() {
                     </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}})}>
                                 <img src={bianji} className="w-[14px] mr-[10px]" alt=""/>
@@ -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}  />

+ 54 - 5
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<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 ?
@@ -173,6 +209,15 @@ export default function Skills() {
                     </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)}>
                                 <img src={bianji} className="w-[14px] mr-[10px]" alt=""/>
@@ -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}  />

+ 72 - 6
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;
+    }
   }

+ 2 - 1
src/util/hook.ts

@@ -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) => {