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

BIN
.DS_Store vendored

Binary file not shown.

View File

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

View File

@@ -356,22 +356,22 @@
"evaluation": { "evaluation": {
"id": "任务ID", "id": "任务ID",
"filename": "测试文件名称", "filename": "测试文件名称",
"skillAssistant": "技能助手", "skillAssistant": "能力NPC",
"status": "状态", "status": "状态",
"score": "评测分数", "score": "评测分数",
"createDate": "创建日期", "createDate": "创建日期",
"download": "下载", "download": "下载",
"confirmDeleteEvaluation": "确认删除该评测任务?", "confirmDeleteEvaluation": "确认删除该评测任务?",
"createTitle": "新建任务", "createTitle": "新建任务",
"selectLabel": "选择要评测的技能或者助手", "selectLabel": "选择要评测的能力或者NPC",
"selectPlaceholder": "请选择", "selectPlaceholder": "请选择",
"dataLabel": "测试集数据:", "dataLabel": "测试集数据:",
"fileExpandName": "支持扩展名:", "fileExpandName": "支持扩展名:",
"downloadTemplate": "下载模板文件", "downloadTemplate": "下载模板文件",
"promptLabel": "评测指令文本:", "promptLabel": "评测指令文本:",
"enterExecType": "请选择要评测的技能或助手", "enterExecType": "请选择要评测的能力或NPC",
"enterUniqueId": "请选择技能或助手ID", "enterUniqueId": "请选择能力或NPCID",
"enterVersion": "请选择能的版本", "enterVersion": "请选择能的版本",
"enterFile": "请选择测试集数据", "enterFile": "请选择测试集数据",
"enterPrompt": "评测指令不能为空", "enterPrompt": "评测指令不能为空",
"fileSizeLimit": "文件大小限制在10M以内", "fileSizeLimit": "文件大小限制在10M以内",

View File

@@ -186,7 +186,21 @@
"fileStorageFailure": " 文件地址失效!", "fileStorageFailure": " 文件地址失效!",
"confirmDeleteChat": "确认删除该会话?", "confirmDeleteChat": "确认删除该会话?",
"roundOver": "本轮结束", "roundOver": "本轮结束",
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动" "chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动",
"feedback": "反馈",
"feedbackRequired": "反馈信息不能为空",
"dialogueSelection": "对话选择",
"chooseSkillOrAssistant": "选择一个您想使用的线上能力或NPC",
"search": "搜索",
"recommendationQuestions": "推荐问题",
"historicalMessages": "以上为历史消息",
"clickDownload": "点击下载",
"searchAssistantOrSkill": "搜索NPC或者能力",
"operationTips": "操作提示:在左侧选择要展示的标签,在右侧拖拽进行排序",
"selected": "已选",
"pleaseSelectAnApp": "请选择一个应用",
"allLabels": "全部标签",
"searchLabels": "搜索标签"
}, },
"model": { "model": {
"modelConfiguration": "模型配置", "modelConfiguration": "模型配置",
@@ -515,6 +529,11 @@
"cancle": "取消", "cancle": "取消",
"tip": "提示", "tip": "提示",
"deleteAssistant": "确认删除该NPC", "deleteAssistant": "确认删除该NPC",
"chatTipsTitle": "使用提示",
"updateSuccess": "修改成功",
"createSuccess": "创建成功",
"confirm": "确认",
"required": "不可为空",
"build": { "build": {
"create": "创建", "create": "创建",
"assistant": "NPC", "assistant": "NPC",
@@ -679,6 +698,12 @@
"endDate": "结束日期", "endDate": "结束日期",
"actionBehavior": "操作行为" "actionBehavior": "操作行为"
}, },
"tag": {
"labelMaxLength": "标签名不能超过10个字符",
"confirmDeleteLabel": "标签【{{label}}】正在使用中,确认删除?",
"createNewLabel": "创建“新标签”",
"addLabel": "添加标签"
},
"agents": { "agents": {
"AgentInitializer": { "AgentInitializer": {
"display_name": "AgentInitializer", "display_name": "AgentInitializer",

View File

@@ -356,22 +356,22 @@
"evaluation": { "evaluation": {
"id": "任务ID", "id": "任务ID",
"filename": "测试文件名称", "filename": "测试文件名称",
"skillAssistant": "技能助手", "skillAssistant": "能力NPC",
"status": "状态", "status": "状态",
"score": "评测分数", "score": "评测分数",
"createDate": "创建日期", "createDate": "创建日期",
"download": "下载", "download": "下载",
"confirmDeleteEvaluation": "确认删除该评测任务?", "confirmDeleteEvaluation": "确认删除该评测任务?",
"createTitle": "新建任务", "createTitle": "新建任务",
"selectLabel": "选择要评测的技能或者助手", "selectLabel": "选择要评测的能力或者NPC",
"selectPlaceholder": "请选择", "selectPlaceholder": "请选择",
"dataLabel": "测试集数据:", "dataLabel": "测试集数据:",
"fileExpandName": "支持扩展名:", "fileExpandName": "支持扩展名:",
"downloadTemplate": "下载模板文件", "downloadTemplate": "下载模板文件",
"promptLabel": "评测指令文本:", "promptLabel": "评测指令文本:",
"enterExecType": "请选择要评测的技能或助手", "enterExecType": "请选择要评测的能力或NPC",
"enterUniqueId": "请选择技能或助手ID", "enterUniqueId": "请选择能力或NPCID",
"enterVersion": "请选择能的版本", "enterVersion": "请选择能的版本",
"enterFile": "请选择测试集数据", "enterFile": "请选择测试集数据",
"enterPrompt": "评测指令不能为空", "enterPrompt": "评测指令不能为空",
"fileSizeLimit": "文件大小限制在10M以内", "fileSizeLimit": "文件大小限制在10M以内",

View File

@@ -186,7 +186,21 @@
"fileStorageFailure": " 文件地址失效!", "fileStorageFailure": " 文件地址失效!",
"confirmDeleteChat": "确认删除该会话?", "confirmDeleteChat": "确认删除该会话?",
"roundOver": "本轮结束", "roundOver": "本轮结束",
"chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动" "chatDialogTip": "设置提示模板中定义的输入变量。与代理和链互动",
"feedback": "反馈",
"feedbackRequired": "反馈信息不能为空",
"dialogueSelection": "对话选择",
"chooseSkillOrAssistant": "选择一个您想使用的线上能力或NPC",
"search": "搜索",
"recommendationQuestions": "推荐问题",
"historicalMessages": "以上为历史消息",
"clickDownload": "点击下载",
"searchAssistantOrSkill": "搜索NPC或者能力",
"operationTips": "操作提示:在左侧选择要展示的标签,在右侧拖拽进行排序",
"selected": "已选",
"pleaseSelectAnApp": "请选择一个应用",
"allLabels": "全部标签",
"searchLabels": "搜索标签"
}, },
"model": { "model": {
"modelConfiguration": "模型配置", "modelConfiguration": "模型配置",
@@ -515,6 +529,11 @@
"cancle": "取消", "cancle": "取消",
"tip": "提示", "tip": "提示",
"deleteAssistant": "确认删除该NPC", "deleteAssistant": "确认删除该NPC",
"chatTipsTitle": "使用提示",
"updateSuccess": "修改成功",
"createSuccess": "创建成功",
"confirm": "确认",
"required": "不可为空",
"build": { "build": {
"create": "创建", "create": "创建",
"assistant": "NPC", "assistant": "NPC",
@@ -679,6 +698,12 @@
"endDate": "结束日期", "endDate": "结束日期",
"actionBehavior": "操作行为" "actionBehavior": "操作行为"
}, },
"tag": {
"labelMaxLength": "标签名不能超过10个字符",
"confirmDeleteLabel": "标签【{{label}}】正在使用中,确认删除?",
"createNewLabel": "创建“新标签”",
"addLabel": "添加标签"
},
"agents": { "agents": {
"AgentInitializer": { "AgentInitializer": {
"display_name": "AgentInitializer", "display_name": "AgentInitializer",

BIN
src/.DS_Store vendored

Binary file not shown.

BIN
src/assets/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

View File

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

View File

@@ -270,7 +270,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
{/* form */} {/* form */}
{ {
formShow && <div className="relative"> 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} {inputForm}
</div> </div>
</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"> <div className="flex absolute left-3 top-4 z-10">
{ {
form && <div 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)} onClick={() => (showWhenLocked || !inputLock.locked) && setFormShow(!formShow)}
><FormIcon className={!showWhenLocked && inputLock.locked ? 'text-gray-400' : 'text-gray-800'}></FormIcon></div> ><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} disabled={inputLock.locked}
onInput={handleTextAreaHeight} onInput={handleTextAreaHeight}
placeholder={inputLock.locked ? inputLock.reason : t('chat.inputPlaceholder')} 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')} // 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) => { onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) { if (event.key === "Enter" && !event.shiftKey) {

View File

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

View File

@@ -7,7 +7,8 @@ import robotU from "../../../assets/robotU.png";
import btnEdit from "../../../assets/chat/btn-edit.png"; import btnEdit from "../../../assets/chat/btn-edit.png";
import btnReSend from "../../../assets/chat/btn-reSend.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 msg = data.message[data.chatKey]
const { appConfig } = useContext(locationContext) const { appConfig } = useContext(locationContext)

View File

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

View File

@@ -1,7 +1,7 @@
import { Badge } from "@/components/bs-ui/badge"; import { Badge } from "@/components/bs-ui/badge";
import { Button } from "@/components/bs-ui/button"; import { Button } from "@/components/bs-ui/button";
import { getChatOnlineApi } from "@/controllers/API/assistant"; 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 { useNavigate } from "react-router-dom";
import { SearchInput } from "../../bs-ui/input"; import { SearchInput } from "../../bs-ui/input";
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "../../bs-ui/sheet"; 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 zidingyi2 from "../../../assets/npc/zidingyi2.png";
import npcIcon from "../../../assets/npc/npcIcon.png"; import npcIcon from "../../../assets/npc/npcIcon.png";
import nengliIcon from "../../../assets/npc/nengliIcon.png"; import nengliIcon from "../../../assets/npc/nengliIcon.png";
import biaoqianPaixu from "../../../assets/chat/biaoqian-paixu.png";
import { useDebounce } from "@/util/hook"; import { useDebounce } from "@/util/hook";
import LoadMore from "../loadMore"; 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 }) { export default function SkillChatSheet({ children, onSelect }) {
const [open, setOpen] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const { user } = useContext(userContext)
const chatListRef = useRef([])
const navigate = useNavigate() 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 [keyword, setKeyword] = useState(' ')
const allDataRef = useRef([]) const allDataRef = useRef([])
const [markLabelOpen, setMarkLabelOpen] = useState(false)
const pageRef = useRef(1)
const searchRef = useRef('')
const [options, setOptions] = useState<any>([])
const loadData = (more = false) => { const loadData = (more = false) => {
open && getChatOnlineApi(pageRef.current, searchRef.current).then(res => { getChatOnlineApi(pageRef.current, searchRef.current, -1).then((res: any) => {
setOptions(opts => more ? [...opts, ...res] : res) 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(() => { useEffect(() => {
// open && getChatOnlineApi().then(res => { debounceLoad()
// allDataRef.current = res getHomeLabelApi().then((res: any) => {
// setKeyword('') setLabels(res.map(d => ({ label: d.name, value: d.id, selected: true })))
// }) })
// setKeyword(' ') }, [])
pageRef.current = 1
searchRef.current = '' const debounceLoad = useDebounce(loadData, 600, false)
loadData()
}, [open])
// const options = useMemo(() => { // const options = useMemo(() => {
// return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase())) // return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase()))
@@ -64,9 +81,26 @@ export default function SkillChatSheet({ children, onSelect }) {
debounceLoad() 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++ pageRef.current++
loadData(true) await debounceLoad(true)
} }
const render = (item: any) => ( const render = (item: any) => (
@@ -109,13 +143,31 @@ export default function SkillChatSheet({ children, onSelect }) {
<SheetTitle></SheetTitle> <SheetTitle></SheetTitle>
<SheetDescription className="text-[#999999]">使线NPC或能力</SheetDescription> <SheetDescription className="text-[#999999]">使线NPC或能力</SheetDescription>
{/* <SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} /> */} {/* <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>
<div className="w-[690px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet"> <div className="w-[690px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
<SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/> <SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
<LoadMore onScrollLoad={handleLoadMore} /> {flag && <LoadMore onScrollLoad={handleLoadMore} />}
</div> </div>
</div> </div>
<MarkLabel open={markLabelOpen} home={labels} onClose={handleClose}></MarkLabel>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
}; };

View File

@@ -5,6 +5,6 @@ export const WordIcon = forwardRef<
SVGSVGElement & { className: any }, SVGSVGElement & { className: any },
React.PropsWithChildren<{ className?: string }> React.PropsWithChildren<{ className?: string }>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const _className = 'transition text-gray-950 ' + (className || '') const _className = 'transition text-[#43AFD2] ' + (className || '')
return <Word ref={ref} {...props} className={_className} />; return <Word ref={ref} {...props} className={_className} />;
}); });

View File

@@ -0,0 +1,2 @@
<svg t="1721124907716" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6119"
width="20" height="20"><path d="M366.8992 790.2208c0 22.528 18.432 40.96 40.96 40.96h208.384c22.528 0 40.96-18.432 40.96-40.96v-23.9616H366.8992v23.9616zM512 115.3024c-166.7072 0-301.8752 135.168-301.8752 301.8752 0 114.176 63.3856 213.4016 156.7744 264.8064v43.3152h290.304V681.984c93.3888-51.3024 156.7744-150.6304 156.7744-264.8064-0.1024-166.7072-135.2704-301.8752-301.9776-301.8752zM592.6912 862.3104H431.3088c-11.264 0-20.48 9.216-20.48 20.48v5.4272c0 11.264 9.216 20.48 20.48 20.48h161.28c11.264 0 20.48-9.216 20.48-20.48v-5.4272c0.1024-11.264-9.1136-20.48-20.3776-20.48z" p-id="6120" fill="#8a8a8a"></path></svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@@ -0,0 +1,10 @@
import React, { forwardRef } from "react";
import { ReactComponent as Prompt } from "./Prompt.svg";
export const PromptIcon = forwardRef<
SVGSVGElement & { className: any },
React.PropsWithChildren<{ className?: string }>
>(({ className, ...props }, ref) => {
const _className = 'transition text-[#999] ' + (className || '')
return <Prompt ref={ref} {...props} className={_className} />;
});

View File

@@ -35,7 +35,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref} ref={ref}
className={cname( 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 className
)} )}
{...props} {...props}
@@ -64,7 +64,7 @@ const AlertDialogFooter = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cname( className={cname(
"flex justify-center flex-col-reverse sm:flex-row sm:space-x-2", "flex justify-center mt-[27px]",
className className
)} )}
{...props} {...props}
@@ -78,7 +78,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
ref={ref} ref={ref}
className={cname("text-lg font-semibold text-center", className)} className={cname("text-[16px] font-bold text-center text-[#fff]", className)}
{...props} {...props}
/> />
)) ))
@@ -90,7 +90,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
ref={ref} 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} {...props}
/> />
)) ))
@@ -103,7 +103,7 @@ const AlertDialogAction = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action
ref={ref} ref={ref}
className={cname(buttonVariants({variant: "destructive"} ), className)} className={cname(buttonVariants({variant: "destructive"} ), className, "baogao-btn ml-[27px]")}
{...props} {...props}
/> />
)) ))
@@ -118,7 +118,8 @@ const AlertDialogCancel = React.forwardRef<
className={cname( className={cname(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0", "mt-2 sm:mt-0",
className className,
"baogao-btn"
)} )}
{...props} {...props}
/> />

View File

@@ -6,7 +6,7 @@ import { MinusCircledIcon } from "@radix-ui/react-icons"
import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons" import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons"
import { useState } from "react" import { useState } from "react"
import sousuo from "../../../assets/npc/sousuo1.png" import sousuo from "../../../assets/npc/sousuo.png"
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> { } extends React.InputHTMLAttributes<HTMLInputElement> { }

View File

@@ -13,10 +13,18 @@ export interface AssistantItemDB {
status: number; 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`, { return await axios.get(`/api/v1/assistant`, {
params: { params: {
page, limit, name page, limit, name,
tag_id: tag_id === -1 ? null : tag_id
} }
}); });
}; };

View File

@@ -148,8 +148,13 @@ export async function saveFlowToDatabase(newFlow: {
* @returns {Promise<any>} The flows data. * @returns {Promise<any>} The flows data.
* @throws Will throw an error if reading fails. * @throws Will throw an error if reading fails.
*/ */
export async function readFlowsFromDatabase(page: number = 1, pageSize: number = 20, search: string) { // 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}`); // 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 }; return { data, total };
} }

View File

@@ -0,0 +1,61 @@
import axios from "../request";
export async function getAllLabelsApi() {
return await axios.get('/api/v1/tag')
}
// admin全局创建一个标签
export async function createLabelApi(name:string) {
return await axios.post('/api/v1/tag', {
name
})
}
// admin修改标签
export async function updateLabelApi(id:number, name:string) {
return await axios.put('/api/v1/tag', {
tag_id: id,
name
})
}
// admin删除标签
export async function deleteLabelApi(id:number) {
return await axios.delete('/api/v1/tag', {
data: {
tag_id: id
}
})
}
// 建立助手或技能和标签的关系,即选择标签
export async function createLinkApi(tag_id:number, resource_id:string, resource_type:number) {
return await axios.post('/api/v1/tag/link', {
tag_id,
resource_id,
resource_type
})
}
// 删除助手或技能和标签的关系,即不选标签
export async function deleteLinkApi(tag_id:number, resource_id:string, resource_type:number) {
return await axios.delete('/api/v1/tag/link', {
data: {
tag_id,
resource_id,
resource_type
}
})
}
// 获取首页展示的标签列表
export async function getHomeLabelApi() {
return await axios.get('/api/v1/tag/home')
}
// 更新首页展示的标签列表
export async function updateHomeLabelApi(tag_ids) {
return await axios.post('/api/v1/tag/home', {
tag_ids
})
}

View File

@@ -99,7 +99,7 @@ export default function MainLayout() {
} }
{ {
isMenu('filelib') && isMenu('knowledge') &&
<NavLink to='/filelib' className="nav-link3 w-[70px] h-[55px] mx-[7px]"> <NavLink to='/filelib' className="nav-link3 w-[70px] h-[55px] mx-[7px]">
{/* <HardDrive /> */} {/* <HardDrive /> */}
<span className="mx-3 max-w-[48px]">{t('menu.knowledge')}</span> <span className="mx-3 max-w-[48px]">{t('menu.knowledge')}</span>

View File

@@ -0,0 +1,158 @@
import { Badge } from "@/components/bs-ui/badge";
import { Button } from "@/components/bs-ui/button";
import { getChatOnlineApi } from "@/controllers/API/assistant";
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { SearchInput } from "@/components/bs-ui/input";
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "@/components/bs-ui/sheet";
import CardComponent, { TitleIconBg } from "@/components/bs-comp/cardComponent";
import { SkillIcon } from "@/components/bs-icons/skill";
import { AssistantIcon } from "@/components/bs-icons/assistant";
import { useTranslation } from "react-i18next";
import borderR from "../../../assets/npc/border-r.png";
import { SpotlightCard } from "@lobehub/ui";
import { Flexbox } from 'react-layout-kit';
import robot from "../../../assets/robot.png";
import robot2 from "../../../assets/robot2.png";
import robot3 from "../../../assets/robot3.png";
import zidingyi1 from "../../../assets/npc/zidingyi1.png";
import zidingyi2 from "../../../assets/npc/zidingyi2.png";
import npcIcon from "../../../assets/npc/npcIcon.png";
import nengliIcon from "../../../assets/npc/nengliIcon.png";
import biaoqianPaixu from "../../../assets/chat/biaoqian-paixu.png";
import sousuo from "../../../assets/npc/sousuo.png";
import shengQue from "../../../assets/chat/shengQue.png";
import { useDebounce } from "@/util/hook";
import LoadMore from "@/components/bs-comp/loadMore";
import { userContext } from "@/contexts/userContext";
import { getHomeLabelApi } from "@/controllers/API/label";
import MarkLabel from "@/pages/ChatAppPage/components/MarkLabel";
export default function HomePage({ onSelect }) {
const { t } = useTranslation()
const { user } = useContext(userContext)
const chatListRef = useRef([])
const navigate = useNavigate()
const [labels, setLabels] = useState([])
const [open, setOpen] = useState(false)
const pageRef = useRef(1)
const [options, setOptions] = useState<any>([])
const searchRef = useRef('')
const [flag, setFlag] = useState(null) // 解决筛选之后再次发起请求覆盖筛选数据
const [keyword, setKeyword] = useState(' ')
const allDataRef = useRef([])
const [markLabelOpen, setMarkLabelOpen] = useState(false)
const loadData = (more = false) => {
getChatOnlineApi(pageRef.current, searchRef.current, -1).then((res: any) => {
setFlag(true)
chatListRef.current = res
setOptions(more ? [...options, ...res] : res)
})
}
useEffect(() => {
debounceLoad()
getHomeLabelApi().then((res: any) => {
setLabels(res.map(d => ({ label: d.name, value: d.id, selected: true })))
})
}, [])
const debounceLoad = useDebounce(loadData, 600, false)
const handleSearch = (e) => {
pageRef.current = 1
searchRef.current = e.target.value
debounceLoad()
}
const handleClose = async (bool) => {
const newHome = await getHomeLabelApi()
// @ts-ignore
setLabels(newHome.map(d => ({ label: d.name, value: d.id, selected: true })))
setMarkLabelOpen(bool)
}
const [chooseId, setChooseId] = useState() // 筛选项样式变化
const handleTagSearch = (id) => {
setChooseId(id)
setFlag(false)
pageRef.current = 1
getChatOnlineApi(pageRef.current, '', id).then((res: any) => {
setOptions(res)
})
}
const handleLoadMore = async () => {
pageRef.current++
await debounceLoad(true)
}
const render = (item: any) => (
<Flexbox align={'flex-start'} className={`selectNpcFlexbox relative`} onClick={() => { onSelect(item); setOpen(false) }}>
<div className="npcInfoItemBg">
<span>
<span>
<div>
<TitleIconBg className="w-[160px] h-[160px] min-w-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
</div>
</span>
</span>
</div>
<div>
<TitleIconBg className="w-[40px] h-[40px] min-w-[40px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
<div>
<p>{item.name}</p>
<div>
</div>
</div>
</div>
<p className="mt-[10px] test-[13px]">{item.desc}</p>
<div className={`absolute right-0 top-0 w-[41px] h-[16px] flex justify-center items-center text-[9px] ${item.flow_type === 'flow' ? 'text-[#333333] bg-[#FFD54C]' : 'text-[#FFFFFF] bg-[#2586FF]'}`} style={{borderRadius:"0px 10px 0px 7px",fontWeight:"bold"}}>
{item.flow_type === 'flow' ? '能力' : 'NPC'}
</div>
</Flexbox>
);
return <div className="h-[100vh]" style={{width: 'calc(100% - 288px)'}}>
<div className="w-[100%] h-full xinDuiHua-box" onClick={e => e.stopPropagation()}>
{/* <div className="xinDuiHua-boxR">
</div> */}
<div className="p-6 flex justify-center items-center" style={{flexDirection:"column"}}>
{/* <SheetTitle>选择对话</SheetTitle>
<SheetDescription className="text-[#999999]">选择一个您想使用的上线NPC或能力</SheetDescription> */}
{/* <SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} /> */}
<p className="text-[#FFFFFF] text-[12px] mt-[30px]"></p>
<p className="text-[#999999] text-[11px] mt-[10px]">使线NPC或能力</p>
<div className="relative my-6">
<SearchInput placeholder="搜索" className="npcInput3 w-[610px] chatHomeInput" onChange={handleSearch} />
<div className="absolute w-[54px] h-[34px] bg-[#FFD54C] right-0 top-0 border-radius-35 flex justify-center items-center cursor-pointer" onClick={handleSearch}><img src={sousuo} className="w-[14px]" alt="" /></div>
</div>
<div className="flex flex-wrap duihuaTab w-[610px]">
{/* @ts-ignore */}
{user.role === 'admin' && <img src={biaoqianPaixu} alt="" className="h-[20px] w-[20px] cursor-pointer mr-[13px]" onClick={() => setMarkLabelOpen(true)} />}
{/* <Button variant={chooseId ? "outline" : "default"} className="mb-2 mr-5" size="sm"
onClick={() => { setChooseId(null); loadData(false) }}>全部</Button> */}
<div className={`h-[20px] px-[13px] mr-[13px] text-[9px] flex justify-center items-center border-radius-14 cursor-pointer ${chooseId ? "text-[#999999] bg-[#1A1A1A]" : "text-[#FFD54C] bg-[#4D4017]"}`} onClick={() => { setChooseId(null); loadData(false) }}></div>
{
labels.map((l, index) => index <= 11 &&
<div className={`h-[20px] px-[13px] mb-[10px] mr-[13px] text-[9px] flex justify-center items-center border-radius-14 cursor-pointer ${l.value === chooseId ? "text-[#FFD54C] bg-[#4D4017]" : "text-[#999999] bg-[#1A1A1A]"}`} onClick={() => handleTagSearch(l.value)}>{l.label}</div>)
}
</div>
</div>
<div className="overflow-y-auto scrollbar-hide skillSheet">
{options.length ? <SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
:<div className="flex justify-center items-center" style={{flexDirection:"column",height:"calc(100% - 210px"}}>
<img src={shengQue} className="w-[176px]" alt="" />
<p className="mt-[26px] text-[#666666] text-[11px]">NPC</p>
</div>}
{flag && <LoadMore onScrollLoad={handleLoadMore} />}
</div>
<MarkLabel open={markLabelOpen} home={labels} onClose={handleClose}></MarkLabel>
</div>
</div>
}

View File

@@ -184,6 +184,7 @@ export default function ChatPanne({ customWsHost = '', appendHistory = false, da
}); });
document.dispatchEvent(myEvent); document.dispatchEvent(myEvent);
} }
if(!appConfig.websocketHost) return
// // ws通信 // // ws通信
// const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version) // const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
// // 停止状态 // // 停止状态

View File

@@ -0,0 +1,146 @@
import { PromptIcon } from '@/components/bs-icons/prompt';
import { Button } from '@/components/bs-ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
import { useTranslation } from 'react-i18next';
import { CrossCircledIcon } from '@radix-ui/react-icons';
import { cname } from '@/components/bs-ui/utils';
import { useEffect, useState } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { updateHomeLabelApi, getAllLabelsApi } from "@/controllers/API/label";
import { captureAndAlertRequestErrorHoc } from '@/controllers/request';
import { useToast } from '@/components/bs-ui/toast/use-toast';
function DragItem({className = '', data, children, onCancel}) {
return <div className={cname('h-7 w-32 relative rounded-xl border flex place-items-center border-[#666] text-[#999]', className)}>
<CrossCircledIcon onClick={(e) => {e.stopPropagation(); onCancel(data.id)}}
className='text-[#999] absolute top-[-6px] right-[-6px] cursor-pointer'/>
<div className='bg-[#999] rounded-xl w-[26px] h-full text-center ml-[-1px]'>
<span className='text-slate-50 font-bold text-sm text-[#262626]'>{data.index}</span>
</div>
<div className='ml-2 truncate'>
{children}
</div>
</div>
}
export default function MarkLabel({open, home, onClose}) {
const { t } = useTranslation()
const [labels, setLabels] = useState([])
const [selected, setSelected] = useState([])
const { message } = useToast()
useEffect(() => {
async function init() {
const all = await getAllLabelsApi()
const newData = all.data.map(d => {
const res = home.find(h => h.value === d.id)
return res ? {label:d.name, value:d.id, selected:true} : {label:d.name, value:d.id, selected:false}
})
setLabels(newData)
setSelected(home)
}
init()
}, [home])
const handleCancel = () => {
onClose(false)
}
const handleConfirm = async () => {
await captureAndAlertRequestErrorHoc(updateHomeLabelApi(selected.map(s => s.value)))
onClose(false)
}
const handleSelect = (id) => {
setLabels(pre => {
const newData = pre.map(l => l.value === id ? {...l, selected:!l.selected} : l)
if(newData.filter(d => d.selected).length > 10) {
message({
title: t('prompt'),
variant: 'warning',
description: '最多选择10个标签'
})
return pre
}
const select = newData.find(d => d.value === id && d.selected)
setSelected(select ? [...selected, select] : pre => pre.filter(d => d.value !== id))
return newData
})
}
const handleDelete = (id) => {
setSelected(pre => pre.filter(d => d.value !== id))
setLabels(pre => pre.map(d => d.value === id ? {...d, selected:!d.selected} : d))
}
const handleDragEnd = (result) => {
if(!result.destination) return
const newData = selected
const [moveItem] = newData.splice(result.source.index, 1)
newData.splice(result.destination.index, 0, moveItem)
setSelected(newData)
setFlag(false)
}
const [flag, setFlag] = useState(false) // 解决拖拽映射位置错位
return <Dialog open={open} onOpenChange={onClose}>
<DialogContent className=' max-w-[70%]'>
<DialogHeader>
<DialogTitle className='flex items-center space-x-2'>
<PromptIcon/>
<span className='text-sm text-[#999]'>{t('chat.operationTips')}</span>
</DialogTitle>
</DialogHeader>
<div className='h-[650px] w-full grid grid-cols-[70%_30%]'>
<div className='ml-[10px]'>
<div className='flex w-[760px] flex-wrap'>
{/* <div className='w-full relative top-[30px] transform -translate-y-[50%] flex'> */}
{
labels.map(l =>
<Button onClick={() => handleSelect(l.value)}
size='sm'
className={`ml-[14px] mb-[10px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333] hover:bg-[#333333]' : 'text-[#333333] bg-[#FFD54C] hover:bg-[#FFD54C]'} w-[120px]`}>
<span className='truncate'>{l.label}</span>
</Button>
// <div onClick={() => handleSelect(l.value)} style={{borderRadius:"4px"}} className={`ml-[14px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333]' : 'text-[#333333] bg-[#FFD54C]'} w-[120px]`}>
// {l.label}
// </div>
)
}
</div>
</div>
<div className='border-l border-[#666]'>
<div className='ml-4'>
<span className='text-md font-bold text-[#999]'>{t('chat.selected')}{selected.length}/10</span>
<DragDropContext onDragEnd={handleDragEnd} onDragStart={() => setFlag(true)} onDragUpdate={() => setFlag(true)}>
<Droppable droppableId={'list'}>
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{selected.map((b,index) => (
<Draggable key={'drag' + b.value} draggableId={'drag' + b.value} index={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}
style={flag ? { ...provided.draggableProps.style, position:'relative', left:0, top:0 } : {...provided.draggableProps.style}}>
<DragItem onCancel={handleDelete} data={{index:index + 1, id:b.value}} className='mt-4 w-[170px]'>
<span className='font-bold text-sm'>{b.label}</span>
</DragItem>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
</div>
<DialogFooter className='absolute bottom-6 right-6'>
<Button variant="outline" className="h-10 w-[120px] px-16" onClick={handleCancel}>{t('cancel')}</Button>
<Button className="px-16 h-10 w-[120px] bg-[#FFD54C] hover:bg-[#FFD54C]" onClick={handleConfirm}>{t('save')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
}

View File

@@ -1,10 +1,12 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
import { Download, Import } from "lucide-react"; 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 { useTranslation } from "react-i18next";
import { getSourceChunksApi, splitWordApi } from "../../../controllers/API"; import { getSourceChunksApi, splitWordApi } from "../../../controllers/API";
import { ChatMessageType } from "../../../types/chat";
import FileView, { checkSassUrl } from "./FileView";
import { downloadFile } from "../../../util/utils"; 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 }) => { const Anwser = ({ id, msg, onInit, onAdd }) => {
@@ -14,12 +16,20 @@ const Anwser = ({ id, msg, onInit, onAdd }) => {
// init // init
useEffect(() => { useEffect(() => {
onInit([]) onInit([])
msg && splitWordApi(msg, id).then((res) => { const loadData = () => {
splitWordApi(msg, id).then((res) => {
// 匹配 // 匹配
const reg = new RegExp(`(${res.join('|')})`, 'g') const reg = new RegExp(`(${res.join('|')})`, 'g')
setHtml(msg.replace(reg, '<span>$1</span>')) setHtml(msg.replace(reg, '<span>$1</span>'))
onInit(res) onInit(res)
}).catch(e => {
// 自动重试
e === '后台处理中,稍后再试' && setTimeout(() => {
loadData()
}, 1800);
}) })
}
msg && loadData()
}, []) }, [])
// add // add
@@ -33,18 +43,43 @@ const Anwser = ({ id, msg, onInit, onAdd }) => {
return () => pRef.current?.removeEventListener('click', handleclick) 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> <p ref={pRef} className="anwser-souce" dangerouslySetInnerHTML={{ __html: html }}></p>
</div> </div>
} }
// //
let timer = null 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 { t } = useTranslation()
const [editCustomKey, setEditCustomKey] = useState(false) const [editCustomKey, setEditCustomKey] = useState(false)
const inputRef = useRef(null) 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) => { const handleAddKeyword = (str: string) => {
setEditCustomKey(false) setEditCustomKey(false)
if (!str) return if (!str) return
@@ -59,7 +94,7 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
// if (!words.length) return setFiles([]) // if (!words.length) return setFiles([])
clearTimeout(timer) // 简单防抖 clearTimeout(timer) // 简单防抖
timer = setTimeout(() => { timer = setTimeout(() => {
getSourceChunksApi(chatId, data.id, words.join(';')).then((_files) => { getSourceChunksApi(chatId, data.messageId, words.join(';')).then((_files) => {
setFiles(_files) setFiles(_files)
// 默认打开第一个文件 // 默认打开第一个文件
_files && setFile(_files[0]) _files && setFile(_files[0])
@@ -78,17 +113,39 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
setTimeout(() => document.getElementById('taginput')?.focus(), 0); setTimeout(() => document.getElementById('taginput')?.focus(), 0);
} }
return <div className="flex gap-4 mt-4" style={{ height: 'calc(100vh - 10rem)' }}> return <div className="flex gap-4 mt-4" style={!isMobile ? { height: 'calc(100vh - 10rem)' } : { height: 'calc(100vh - 4rem)' }}>
{/* left */} {
<div className="w-[300px] bg-gray-100 rounded-md py-4 px-2 h-full overflow-y-auto no-scrollbar"> 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">
{/* label */} {!collapse && <span onClick={() => { setCollapse(true) }} className=""></span>}
<div className="mb-4 text-sm font-bold"> {collapse && <span onClick={() => { setCollapse(false) }} className=""></span>}
{t('chat.filterLabel')}
<div className="tooltip fixed" data-tip={t('chat.tooltipText')}><span data-theme="light" className="badge cursor-pointer">?</span></div>
</div> </div>
<div className="flex flex-wrap gap-2"> }
{words.map((str, i) => <div key={str} className="badge badge-info h-[auto] gap-2 text-gray-600 bg-[rgba(53,126,249,.15)]">{str}<span className="cursor-pointer" onClick={() => onClose(i)}>x</span></div>)} {
{/* 自定义 */} isMobile && <div className="absolute top-2 right-4 z-10 bg-gray-100 dark:bg-gray-950 py-1 px-2 pb-2 rounded-md">
<span onClick={closeDialog} ></span>
</div>
}
{/* left */}
{
(!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" 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) => { onKeyDown={(event) => {
@@ -106,7 +163,7 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
<div className="mt-4"> <div className="mt-4">
<p className="mb-4 text-sm font-bold">{t('chat.sourceDocumentsLabel')}</p> <p className="mb-4 text-sm font-bold">{t('chat.sourceDocumentsLabel')}</p>
{files.map(_file => {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'}`}> _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> <p className="text-sm break-all">{_file.fileName}</p>
<div className="absolute right-1 top-1 gap-2 hidden group-hover:flex"> <div className="absolute right-1 top-1 gap-2 hidden group-hover:flex">
{ {
@@ -134,14 +191,17 @@ const ResultPanne = ({ chatId, words, data, onClose, onAdd, children }: { chatId
{!files.length && <p className="text-sm text-center mt-10 text-gray-500">{t('chat.noMatchedFilesMessage')}</p>} {!files.length && <p className="text-sm text-center mt-10 text-gray-500">{t('chat.noMatchedFilesMessage')}</p>}
</div> </div>
</div> </div>
}
{/* file pane */} {/* file pane */}
{file && children(file)} {file && children(file)}
</div> </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 // labels
const { t } = useTranslation() const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [keywords, setKeywords] = useState([]) const [keywords, setKeywords] = useState([])
const handleAddWord = (word: string) => { const handleAddWord = (word: string) => {
// 去重 更新 // 去重 更新
@@ -152,38 +212,35 @@ export default function ResouceModal({ chatId, data, open, setOpen }: { chatId:
setKeywords(keywords.filter((wd, i) => i !== index)) setKeywords(keywords.filter((wd, i) => i !== index))
} }
// 记忆 const [data, setData] = useState<any>({})
// useEffect(() => { useImperativeHandle(ref, () => ({
// const KEYWORDS_LOCAL_KEY = 'KEYWORDS_LOCAL' openModal: (data) => {
// setKeywords(JSON.parse(localStorage.getItem(KEYWORDS_LOCAL_KEY) || '[]')) setOpen(true)
setData(data)
}
}));
// return () => localStorage.setItem(KEYWORDS_LOCAL_KEY, JSON.stringify(keywords))
// }, [])
const MemoizedFileView = React.memo(FileView); const MemoizedFileView = React.memo(FileView);
return <dialog className={`modal bg-blur-shared ${open ? 'modal-open' : 'modal-close'}`} onClick={() => setOpen(false)}> return <Dialog open={open} onOpenChange={setOpen} >
<div className=" rounded-xl px-4 py-6 bg-[#fff] shadow-lg dark:bg-background w-[80%]" onClick={e => e.stopPropagation()}> <DialogContent className="min-w-[80%]">
{/* <DialogHeader>
<DialogTitle>{t('chat.feedback')}</DialogTitle>
</DialogHeader> */}
{open && <div> {open && <div>
<Anwser id={data.id} msg={data.message || data.thought} onInit={setKeywords} onAdd={handleAddWord}></Anwser> <Anwser id={data.messageId} msg={data.message} onInit={setKeywords} onAdd={handleAddWord}></Anwser>
<ResultPanne words={keywords} chatId={chatId} data={data} onClose={handleDelKeyword} onAdd={handleAddWord}> <ResultPanne words={keywords} chatId={data.chatId} data={data} onClose={handleDelKeyword} onAdd={handleAddWord} closeDialog={()=>setOpen(false)}>
{ {
(file) => file.fileUrl ? (file) => file.fileUrl ?
<MemoizedFileView data={file}></MemoizedFileView> : <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> <p className="text-gray-500 text-md mt-[40%]">{t('chat.fileStorageFailure')}</p>
</div> </div>
} }
</ResultPanne> </ResultPanne>
</div>} </div>}
</div> </DialogContent>
</dialog> </Dialog>
}; });
// const useRefState = (state) => { export default ResouceModal
// const [data, setData] = useState(state)
// const ref = useRef(state)
// return [data, ref, (nState) => {
// setData(nState)
// ref.current = nState
// }]
// }

View File

@@ -22,8 +22,12 @@ import SkillChatSheet from "@/components/bs-comp/sheets/SkillChatSheet";
import { TitleIconBg } from "@/components/bs-comp/cardComponent"; import { TitleIconBg } from "@/components/bs-comp/cardComponent";
import npcIcon from "../../assets/npc/npcIcon.png"; import npcIcon from "../../assets/npc/npcIcon.png";
import nengliIcon from "../../assets/npc/nengliIcon.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 { useMessageStore } from "@/components/bs-comp/chatComponent/messageStore";
import { formatDate } from "@/util/utils"; import { formatDate } from "@/util/utils";
import { message } from "@/components/bs-ui/toast/use-toast";
import HomePage from "./components/ChatHome";
export default function SkillChatPage() { export default function SkillChatPage() {
@@ -70,8 +74,15 @@ export default function SkillChatPage() {
// const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList() // const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList()
const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat, onScrollLoad } = useChatList() const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat, onScrollLoad } = useChatList()
const [location, setLocation] = useState(true)
// select flow // select flow
const handlerSelectFlow = async (card) => { const handlerSelectFlow = async (card) => {
console.log(card)
if (!location) {
setLocation(true)
return
}
if (card) {
// 会话ID // 会话ID
const _chatId = generateUUID(32) const _chatId = generateUUID(32)
// setOpen(false) // setOpen(false)
@@ -87,13 +98,18 @@ export default function SkillChatPage() {
"avatar_img": card.avatar_img, "avatar_img": card.avatar_img,
"avatar_color": card.avatar_color, "avatar_color": card.avatar_color,
}) })
setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type }) setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type })
setChatId(_chatId) setChatId(_chatId)
setLocation(false)
} else {
return message({ title: t('prompt'), variant: 'warning', description: t('chat.pleaseSelectAnApp') })
}
} }
// select chat // select chat
const handleSelectChat = useDebounce(async (chat) => { const handleSelectChat = useDebounce(async (chat) => {
setLocation(false)
// console.log('chat.id :>> ', chat); // console.log('chat.id :>> ', chat);
if (chat.chat_id === chatId) return if (chat.chat_id === chatId) return
setSelelctChat({ id: chat.flow_id, chatId: chat.chat_id, type: chat.flow_type }) 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"> return <div className="flex">
<div className="h-screen w-[288px] relative"> <div className="h-screen w-[288px] relative">
<div className="xinDuiHua absolute bg-[#000000] xinDuiHua-box"> <div className="xinDuiHua absolute bg-[#000000] xinDuiHua-box">
<SkillChatSheet onSelect={handlerSelectFlow}> <div id="newchat" onClick={() => handlerSelectFlow(null)} className="xinDuiHua-btn cursor-pointer">
<div id="newchat" className="xinDuiHua-btn cursor-pointer">
{/* <PlusBoxIcon className="dark:hidden"></PlusBoxIcon> */}
{/* <PlusBoxIconDark className="hidden dark:block"></PlusBoxIconDark> */}
{t('chat.newChat')} {t('chat.newChat')}
</div> </div>
</SkillChatSheet> {/* <SkillChatSheet onSelect={handlerSelectFlow}>
</SkillChatSheet> */}
{/* <div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div> */} {/* <div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div> */}
{/* <div className="xinDuiHua-del cursor-pointer"> {/* <div className="xinDuiHua-del cursor-pointer">
<img src={duihuaDel} alt=""/> <img src={duihuaDel} alt=""/>
@@ -146,6 +161,7 @@ export default function SkillChatPage() {
<p className="text-[12px] text-[#ccc]">{chat.flow_name}</p> <p className="text-[12px] text-[#ccc]">{chat.flow_name}</p>
{/* <div>离线</div> */} {/* <div>离线</div> */}
<p className="text-[9px] text-[#666] mt-[6px] w-[95%]">{chat.latest_message?.message || ''}</p> <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> </div>
<div> <div>
@@ -163,19 +179,34 @@ export default function SkillChatPage() {
</div> </div>
{/* chat */} {/* chat */}
{/* <ChatPanne data={selectChat}></ChatPanne> */} {/* <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 location
? <HomePage onSelect={handlerSelectFlow}></HomePage> ? <HomePage onSelect={handlerSelectFlow}></HomePage>
: <ChatPanne appendHistory data={selectChat}></ChatPanne> : <ChatPanne appendHistory data={selectChat}></ChatPanne>
} */} }
{/* 选择对话技能 */} {/* 选择对话技能 */}
<SkillTemps {/* <SkillTemps
flows={onlineFlows} flows={onlineFlows}
title={t('chat.skillTempsTitle')} title={t('chat.skillTempsTitle')}
desc={t('chat.skillTempsDesc')} desc={t('chat.skillTempsDesc')}
open={open} setOpen={setOpen} open={open} setOpen={setOpen}
onSelect={handlerSelectFlow}></SkillTemps> onSelect={handlerSelectFlow}></SkillTemps> */}
</div> </div>
}; };
/** /**

View File

@@ -11,7 +11,6 @@ export default function chatAssitantShare() {
const wsUrl = `/api/v2/assistant/chat/${assitId}` const wsUrl = `/api/v2/assistant/chat/${assitId}`
const [data] = useState<any>({ id: assitId, chatId: generateUUID(32), type: 'assistant' }) const [data] = useState<any>({ id: assitId, chatId: generateUUID(32), type: 'assistant' })
if (!assitId) return <div></div> if (!assitId) return <div></div>
// return <div className="chatShare"> // return <div className="chatShare">

View File

@@ -39,7 +39,7 @@ export default function FilesPage() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [title, setTitle] = useState('') 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 => { readFileByLibDatabase({ ...param, id, name: param.keyword }).then(res => {
setHasPermission(res.writeable) setHasPermission(res.writeable)
return res return res
@@ -123,13 +123,15 @@ export default function FilesPage() {
} }
return <div className="w-full h-screen p-6 relative overflow-y-auto"> 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> <span className="loading loading-infinity loading-lg"></span>
</div>} </div>} */}
<ShadTooltip content="back" side="top"> <ShadTooltip content="back" side="top">
<Link to='/filelib'>
<button className="extra-side-bar-buttons w-[36px] absolute top-[26px]" onClick={() => { }} > <button className="extra-side-bar-buttons w-[36px] absolute top-[26px]" onClick={() => { }} >
<Link to='/filelib'><ArrowLeft className="side-bar-button-size" /></Link> <ArrowLeft className="side-bar-button-size" />
</button> </button>
</Link>
</ShadTooltip> </ShadTooltip>
<Tabs defaultValue="account" className="w-full"> <Tabs defaultValue="account" className="w-full">
{/* <TabsList className="ml-12"> {/* <TabsList className="ml-12">
@@ -138,9 +140,9 @@ export default function FilesPage() {
</TabsList> */} </TabsList> */}
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-[16px] ml-[40px]" style={{color:"#FFF"}}>{t('lib.fileData')}</p> <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=""/> */} {/* <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>
</div> </div>
<TabsContent value="account"> <TabsContent value="account">
@@ -225,11 +227,11 @@ export default function FilesPage() {
<UploadModal id={id} accept={appConfig.libAccepts} open={open} setOpen={handleOpen} onResult={handleUploadResult}></UploadModal> <UploadModal id={id} accept={appConfig.libAccepts} open={open} setOpen={handleOpen} onResult={handleUploadResult}></UploadModal>
{/* 重复文件提醒 */} {/* 重复文件提醒 */}
<dialog className={`modal ${repeatFiles.length && 'modal-open'}`}> <dialog className={`modal ${repeatFiles.length && 'modal-open'}`}>
<div className="modal-box w-[560px] bg-[#fff] shadow-lg dark:bg-background"> <div className="modal-box w-[560px] bg-[#262626] shadow-lg">
<h3 className="font-bold text-lg relative"> <h3 className="font-bold text-lg relative text-[#fff]">
<X className="absolute right-0 top-0 text-gray-400 cursor-pointer" size={20} onClick={() => setRepeatFiles([])}></X> <X className="absolute right-0 top-0 text-[#fff] cursor-pointer" size={20} onClick={() => setRepeatFiles([])}></X>
</h3> </h3>
<p className="py-4"></p> <p className="py-4 text-[#fff]"></p>
<ul className="overflow-y-auto max-h-[400px]"> <ul className="overflow-y-auto max-h-[400px]">
{repeatFiles.map(el => ( {repeatFiles.map(el => (
<li key={el.id} className="py-2 text-red-500">{el.remark}</li> <li key={el.id} className="py-2 text-red-500">{el.remark}</li>
@@ -237,7 +239,7 @@ export default function FilesPage() {
</ul> </ul>
<div className="modal-action"> <div className="modal-action">
<Button className="h-8 rounded-full" variant="outline" onClick={() => setRepeatFiles([])}></Button> <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>} {retryLoad && <span className="loading loading-spinner loading-xs"></span>}
</Button> </Button>
</div> </div>

View File

@@ -36,8 +36,8 @@ export function transformEvent(event: string): string {
export function transformObjectType(object: string): string { export function transformObjectType(object: string): string {
switch(object) { switch(object) {
case 'none': return '无' case 'none': return '无'
case 'flow': return '能' case 'flow': return '能'
case 'assistant': return '助手' case 'assistant': return 'NPC'
case 'knowledge': return '知识库' case 'knowledge': return '知识库'
case 'file': return '文件' case 'file': return '文件'
case 'user_conf': return '用户配置' case 'user_conf': return '用户配置'

View File

@@ -23,7 +23,7 @@ import sousuo from "../../assets/npc/sousuo.png";
import del from "../../assets/npc/del.png"; import del from "../../assets/npc/del.png";
import zidingyi1 from "../../assets/npc/zidingyi1.png"; import zidingyi1 from "../../assets/npc/zidingyi1.png";
import zidingyijia from "../../assets/npc/zidingyijia.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 { SpotlightCard } from "@lobehub/ui";
import { Flexbox } from 'react-layout-kit'; import { Flexbox } from 'react-layout-kit';
import { updataOnlineState } from "@/controllers/API/flow"; import { updataOnlineState } from "@/controllers/API/flow";
@@ -32,17 +32,35 @@ import { userContext } from "@/contexts/userContext";
import ShareLink from "./externalUse/shareLink"; import ShareLink from "./externalUse/shareLink";
import npcIcon from "../../assets/npc/npcIcon.png"; import npcIcon from "../../assets/npc/npcIcon.png";
import { Button } from "@/components/ui/button"; 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() { export default function Assistants() {
const { t } = useTranslation() const { t } = useTranslation()
const { user } = useContext(userContext); const { user } = useContext(userContext);
const navigate = useNavigate() const navigate = useNavigate()
const { message } = useToast() const { message } = useToast()
const [labels, setLabels] = useState<any[]>([])
const labelsRef = useRef([])
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [isShareLink, setIsShareLink] = useState(false) const [isShareLink, setIsShareLink] = useState(false)
const [isShareLinkData, setIsShareLinkData] = useState("Any"); 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) => 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){ if(dataSource == "" || dataSource[0].type != 0){
@@ -106,11 +124,25 @@ export default function Assistants() {
// })) // }))
return captureAndAlertRequestErrorHoc(changeAssistantStatusApi(item.id, bln ? 1 : 0)).then(res => { return captureAndAlertRequestErrorHoc(changeAssistantStatusApi(item.id, bln ? 1 : 0)).then(res => {
if (res === null) { if (res === null) {
reload();
refreshData((item1) => item1.id === item.id, { status: bln ? 1 : 0 }) refreshData((item1) => item1.id === item.id, { status: bln ? 1 : 0 })
} }
return res 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( return(
<Flexbox align={'flex-start'}> <Flexbox align={'flex-start'}>
{item.type == 0 ? {item.type == 0 ?
@@ -156,6 +188,15 @@ export default function Assistants() {
<p className="ml-[14px]">{item.name}</p> <p className="ml-[14px]">{item.name}</p>
</div> </div>
<p>{item.desc}</p> <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>
<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}})}> {!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => item.status !== 1 && navigate('/assistant/' + item.id,{state: { value:inputRef.current.value, page:page}})}>
@@ -170,6 +211,9 @@ export default function Assistants() {
{item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}> {item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
<img src={del} className="w-[14px]" alt=""/> <img src={del} className="w-[14px]" alt=""/>
</div>} </div>}
<div>
<span className="text-[9px] text-[#666666] ml-[14px]">{item.user_name}</span>
</div>
</div> </div>
{/* <Switch checked={openSwitch} onCheckedChange={handleChange} /> */} {/* <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} /> <Switch checked={openSwitch} onClick={() => !item.write && item.status !== 1 && message({ title: t('prompt'), description: t('skills.contactAdmin'), variant: 'warning' })} onCheckedChange={item.write && handleChange} />

View File

@@ -1,7 +1,7 @@
import SkillTempSheet from "@/components/bs-comp/sheets/SkillTempSheet"; import SkillTempSheet from "@/components/bs-comp/sheets/SkillTempSheet";
import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
import { useToast } from "@/components/bs-ui/toast/use-toast"; 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 { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import CardComponent, { TitleIconBg } from "../../components/bs-comp/cardComponent"; import CardComponent, { TitleIconBg } from "../../components/bs-comp/cardComponent";
@@ -37,6 +37,8 @@ import Templates from "./temps";
import { Size } from "recharts/types/util/types"; import { Size } from "recharts/types/util/types";
import SkillTemps from "./components/SkillTemps"; import SkillTemps from "./components/SkillTemps";
import nengliIcon from "../../assets/npc/nengliIcon.png"; 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() { export default function Skills() {
const { t } = useTranslation() const { t } = useTranslation()
@@ -45,7 +47,7 @@ export default function Skills() {
const navigate = useNavigate() const navigate = useNavigate()
const { page, pageSize, data: dataSource, total, loading, setPage, search, reload, refreshData } = useTable<FlowType>({ pageSize: 14 }, (param) => 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){ 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 nvaigate = useNavigate()
const handleEdit = (id,pageNo) => { const handleEdit = (id,pageNo) => {
// onBeforeEdit?.() // onBeforeEdit?.()
@@ -134,15 +152,33 @@ export default function Skills() {
const [openSwitch, setOpenSwitch] = useState(item.status === 2); const [openSwitch, setOpenSwitch] = useState(item.status === 2);
const handleChange = (bln) => { const handleChange = (bln) => {
captureAndAlertRequestErrorHoc(updataOnlineState(item.id, item, bln).then(res => { captureAndAlertRequestErrorHoc(updataOnlineState(item.id, item, bln).then(res => {
setOpenSwitch(bln); // setOpenSwitch(bln);
if (res) { if (res) {
reload();
refreshData((item1) => item1.id === item.id, { status: bln ? 2 : 1 }) refreshData((item1) => item1.id === item.id, { status: bln ? 2 : 1 })
} }
return res 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( return(
<Flexbox align={'flex-start'}> <Flexbox align={'flex-start'}>
{item.type == 0 ? {item.type == 0 ?
@@ -172,6 +208,15 @@ export default function Skills() {
<p className="ml-[14px] yichu">{item.name}</p> <p className="ml-[14px] yichu">{item.name}</p>
</div> </div>
<p>{item.description}</p> <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>
<div> <div>
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => handleSetting(item)}> {!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => handleSetting(item)}>
@@ -190,6 +235,10 @@ export default function Skills() {
{item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}> {item.write && <div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
<img src={del} className="w-[14px]" alt=""/> <img src={del} className="w-[14px]" alt=""/>
</div>} </div>}
<div>
<span className="text-[9px] text-[#666666] ml-[14px]">{item.user_name}</span>
</div>
</div> </div>
{/* <Switch checked={openSwitch} onCheckedChange={handleChange} /> */} {/* <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} /> <Switch checked={openSwitch} onClick={() => !item.write && item.status !== 2 && message({ title: t('prompt'), description: "请联系管理员上线能力", variant: 'warning' })} onCheckedChange={item.write && handleChange} />

View File

@@ -2712,7 +2712,7 @@
// justify-content:space-between; // justify-content:space-between;
/* padding: 14px; */ /* padding: 14px; */
// position: relative; // position: relative;
padding-bottom: 55px; padding-bottom: 50px;
>div:nth-of-type(2){ >div:nth-of-type(2){
display: flex; display: flex;
align-items: center; align-items: center;
@@ -2733,6 +2733,33 @@
margin-top: 8px; margin-top: 8px;
} }
>div:nth-of-type(3){ >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%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -3845,7 +3872,16 @@
height: 15px!important; height: 15px!important;
border: 1px solid #ffd025!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{ .build-tab{
display: flex; display: flex;
@@ -4009,6 +4045,24 @@
// border: none!important; // border: none!important;
outline: 1px solid #997e1f!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{ .npcInput4{
background: #000000!important; background: #000000!important;
@@ -4194,14 +4248,21 @@
} }
} }
} }
/* 修改垂直滚动条的颜色 */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0; width: 10px; /* 设置滚动条的宽度 */
height: 10px;
} }
::-webkit-scrollbar-horizontal {
::-webkit-scrollbar-track {
// background-color: #666; /* 设置滚动条的背景颜色 */
display: none; display: none;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
display: none; background-color: #333; /* 设置滚动条的颜色 */
border-radius: 20px;
} }
.SelectTrigger{ .SelectTrigger{
@@ -4258,3 +4319,8 @@
.bg-reset{ .bg-reset{
box-shadow: 0px 3px 14px 0px rgba(255,255,255,0.2)!important; box-shadow: 0px 3px 14px 0px rgba(255,255,255,0.2)!important;
} }
.chatHomeInput{
input{
width: 100%!important;
}
}

View File

@@ -109,7 +109,8 @@ export function useTable<T extends object>(param, apiFun) {
// 数据过滤 // 数据过滤
filterData: (p) => { filterData: (p) => {
paramRef.current = { ...paramRef.current, ...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) => { refreshData: (compareFn, data) => {