1
BIN
src/.DS_Store
vendored
@@ -191,9 +191,9 @@
|
||||
}
|
||||
|
||||
path.react-flow__edge-interaction:hover {
|
||||
stroke: #eebbbb;
|
||||
stroke: #ffe999;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 12;
|
||||
stroke-width: 10;
|
||||
}
|
||||
|
||||
.selection-tool-box {
|
||||
|
||||
BIN
src/assets/.DS_Store
vendored
BIN
src/assets/chat/.DS_Store
vendored
BIN
src/assets/chat/xinjian1.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/chat/xinjian2.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
src/assets/npc/.DS_Store
vendored
BIN
src/assets/npc/border-r.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/npc/jianhua.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/npc/lingcun.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/npc/nengliIcon1.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/npc/tuichu.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/npc/xiaoxi.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/npc/图层 1863@2x (1).png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
@@ -60,8 +60,11 @@ export const gradients = [
|
||||
// 'bg-fuchsia-700',
|
||||
// 'bg-pink-600',
|
||||
// 'bg-rose-600'
|
||||
export function TitleIconBg({ id, className = '', children = <SkillIcon /> }) {
|
||||
return <div className={cname(`flex justify-center items-center cursor-pointer ${gradients[parseInt(id + '', 16) % gradients.length]}`, className)} style={{borderRadius:"7px"}}>{children}</div>
|
||||
export function TitleIconBg({ img, id, className = '', children = <SkillIcon /> }) {
|
||||
if(img) return <div className={cname(`flex justify-center items-center cursor-pointer overflow-hidden`, className)} style={{borderRadius:"7px"}}>
|
||||
{children}
|
||||
</div>
|
||||
return <div className={cname(`flex justify-center items-center cursor-pointer ${id!="" && gradients[parseInt(id + '', 16) % gradients.length]}`, className)} style={{borderRadius:"7px"}}>{children}</div>
|
||||
}
|
||||
|
||||
export default function CardComponent<T>({
|
||||
|
||||
@@ -232,11 +232,11 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
}
|
||||
|
||||
return <div className="absolute bottom-0 w-full bg-[#fff] dark:bg-[#000000]">
|
||||
<div className={`relative`}>
|
||||
<div className={`relative pt-[10px]`}>
|
||||
{/* form */}
|
||||
{
|
||||
formShow && <div className="relative">
|
||||
<div className="absolute left-0 border bottom-2 bg-[#fff] px-4 py-2 rounded-md w-[50%] min-w-80">
|
||||
<div className="absolute left-0 bottom-2 bg-[#1a1a1a] px-4 py-2 rounded-md w-[50%] min-w-80">
|
||||
{inputForm}
|
||||
</div>
|
||||
</div>
|
||||
@@ -296,7 +296,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
<p className="w-[100%] text-center text-[#666666]">内容由AI生成,仅供参考</p>
|
||||
{/* <p className="w-[100%] text-center text-[#666666]">内容由AI生成,仅供参考</p> */}
|
||||
</div>
|
||||
<p className="text-center text-sm pt-2 pb-4 text-gray-400">{appConfig.dialogTips}</p>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,17 @@ import remarkMath from "remark-math";
|
||||
import MessageButtons from "./MessageButtons";
|
||||
import SourceEntry from "./SourceEntry";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robotU from "../../../assets/robotU.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import btnEdit from "../../../assets/chat/btn-edit.png";
|
||||
import btnReSend from "../../../assets/chat/btn-reSend.png";
|
||||
import btnDel from "../../../assets/chat/btn-del.png";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
import { TitleIconBg } from "../cardComponent";
|
||||
import Thumbs from "@/pages/ChatAppPage/components/Thumbs";
|
||||
|
||||
// 颜色列表
|
||||
const colorList = [
|
||||
@@ -76,27 +87,51 @@ export default function MessageBs({ data, onUnlike = () => { }, onSource }: { da
|
||||
}
|
||||
|
||||
const chatId = useMessageStore(state => state.chatId)
|
||||
|
||||
console.log(data)
|
||||
return <div className="flex w-full py-1">
|
||||
<div className="w-fit max-w-[90%]">
|
||||
{data.sender && <p className="text-gray-600 text-xs mb-2">{data.sender}</p>}
|
||||
<div className="ml-[14px] min-h-8 px-6 py-4 rounded-2xl bg-[#13110D] dark:bg-[#13110D] text-[#fff]">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-start avatarZk">
|
||||
{/* {(data.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || data.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{data.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(data.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && data.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && data.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>} */}
|
||||
{data.flow_id && <TitleIconBg className="w-[40px] h-[40px]" img={data.avatar_img} id={data.avatar_color ? data.avatar_color : data.flow_id} ><img src={data.avatar_img ? data.avatar_img : (data.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>}
|
||||
|
||||
<div ref={messageRef} className={`min-h-8 min-w-[110px] max-w-[50vw] ml-[10px] ${data.id && data.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{data.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!data.id && data.end && <Thumbs
|
||||
id={data.id}
|
||||
data={data.liked}
|
||||
onCopy={handleCopyMessage}
|
||||
onDislike={onUnlike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
}
|
||||
</div>
|
||||
{/* <div className="flex gap-2">
|
||||
<div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
|
||||
{data.message.toString() ?
|
||||
<div ref={messageRef} className="text-sm max-w-[calc(100%-24px)]">
|
||||
{mkdown}
|
||||
{mkdown} */}
|
||||
{/* @user */}
|
||||
{data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>}
|
||||
{/* {data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>} */}
|
||||
{/* 光标 */}
|
||||
{/* {data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>} */}
|
||||
</div>
|
||||
{/* </div>
|
||||
: <div><LoadIcon className="text-gray-400" /></div>
|
||||
}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
{/* 附加信息 */}
|
||||
{
|
||||
{/* {
|
||||
!!data.id && data.end && <div className="flex justify-between mt-2">
|
||||
<SourceEntry
|
||||
extra={data.extra}
|
||||
@@ -115,7 +150,7 @@ export default function MessageBs({ data, onUnlike = () => { }, onSource }: { da
|
||||
onCopy={handleCopyMessage}
|
||||
></MessageButtons>
|
||||
</div>
|
||||
}
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function MessagePanne({ useName, guideWord, loadMore }) {
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, messages, chatId]);
|
||||
|
||||
return <div id="message-panne" ref={messagesRef} className="h-full overflow-y-auto scrollbar-hide pt-12 pb-60">
|
||||
return <div id="message-panne" ref={messagesRef} className="h-full overflow-y-auto scrollbar-hide pt-[50px] pb-60 px-3">
|
||||
{guideWord && <MessageBs
|
||||
key={9999}
|
||||
data={{ message: guideWord, isSend: false, chatKey: '', end: true, user_name: '' }} />}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function MessageSystem({ data }) {
|
||||
() => (
|
||||
data.thought && <ReactMarkdown
|
||||
linkTarget="_blank"
|
||||
className="bs-mkdown text-gray-600 dark:text-[white] inline-block break-all max-w-full text-sm [&>pre]:text-wrap"
|
||||
className="markdown text-gray-600 inline-block break-all max-w-full text-sm"
|
||||
>
|
||||
{data.thought.toString()}
|
||||
</ReactMarkdown>
|
||||
@@ -33,9 +33,10 @@ export default function MessageSystem({ data }) {
|
||||
)
|
||||
|
||||
const border = { system: 'border-slate-500', question: 'border-amber-500', processing: 'border-cyan-600', answer: 'border-lime-600', report: 'border-slate-500', guide: 'border-none' }
|
||||
const style = { system: 'style-system', question: 'style-question', processing: 'border-cyan-600', answer: 'style-answer', report: 'style-system' }
|
||||
|
||||
return <div className="py-1">
|
||||
<div className={`relative rounded-sm px-6 py-4 border text-sm ${data.category === 'guide' ? 'bg-[#EDEFF6]' : 'bg-slate-50'} ${border[data.category || 'system']}`}>
|
||||
<div className={`log rounded-xl whitespace-pre-wrap mt-[14px] relative ${style[data.category || 'system']}`}>
|
||||
{logMkdown}
|
||||
{data.category === 'report' && <CopyIcon className=" absolute right-4 top-2 cursor-pointer" onClick={(e) => handleCopy(e.target.parentNode)}></CopyIcon>}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,9 @@ import { ChatMessageType } from "@/types/chat";
|
||||
import { MagnifyingGlassIcon, Pencil2Icon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { useContext } from "react";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
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 }) {
|
||||
const msg = data.message[data.chatKey]
|
||||
@@ -25,16 +28,33 @@ export default function MessageUser({ useName, data }: { data: ChatMessageType }
|
||||
}
|
||||
|
||||
return <div className="flex justify-end w-full py-1">
|
||||
<div className="w-fit min-h-8 max-w-[90%]">
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{/* {chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>} */}
|
||||
{msg}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!running && <img src={btnEdit} onClick={() => handleResend(false)} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!running && <img src={btnReSend} onClick={() => handleResend(true)} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <p className="mr-[20px] text-[14px]">{userName}</p> */}
|
||||
<img src={robotU} className="w-[50px]" alt=""/>
|
||||
</div>
|
||||
{/* <div className="w-fit min-h-8 max-w-[90%]">
|
||||
{useName && <p className="text-gray-600 text-xs mb-2 text-right">{useName}</p>}
|
||||
<div className="mr-[14px] rounded-2xl px-6 py-4 bg-[#EEF2FF] dark:bg-[#333A48]">
|
||||
<div className="flex gap-2 ">
|
||||
<div className="text-[#0D1638] dark:text-[#CFD5E8] text-sm break-all whitespace-break-spaces">{msg}</div>
|
||||
<div className="w-6 h-6 min-w-6"><img src="/user.png" alt="" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* 附加信息 */}
|
||||
{
|
||||
{/* {
|
||||
// 数组类型的 data通常是文件上传消息,不展示附加按钮
|
||||
!Array.isArray(data.message.data) && <div className="flex justify-between mt-2 mr-[14px]">
|
||||
<span></span>
|
||||
@@ -45,6 +65,6 @@ export default function MessageUser({ useName, data }: { data: ChatMessageType }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function RunLog({ data }) {
|
||||
return <div className="py-1">
|
||||
<div className="rounded-sm border">
|
||||
<div className="flex justify-between items-center px-4 py-2 cursor-pointer" onClick={() => setOpen(!open)}>
|
||||
<div className="flex items-center font-bold gap-2 text-sm">
|
||||
<div className="flex items-center font-bold gap-2 text-sm text-[#fff]">
|
||||
{
|
||||
data.end ? <ToastIcon type={lost ? 'error' : 'success'} /> :
|
||||
<LoadIcon className="text-primary duration-300" />
|
||||
@@ -50,7 +50,7 @@ export default function RunLog({ data }) {
|
||||
</div>
|
||||
<CaretDownIcon className={open && 'rotate-180'} />
|
||||
</div>
|
||||
<div className={cname('bg-gray-100 px-4 py-2 text-gray-500 overflow-hidden text-sm ', open ? 'h-auto' : 'h-0 p-0')}>
|
||||
<div className={cname('bg-[#0B1F26] px-4 py-2 text-[#ccc] overflow-hidden text-sm ', open ? 'h-auto' : 'h-0 p-0')}>
|
||||
<p>{data.thought}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,8 +26,8 @@ type State = {
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
loadHistoryMsg: (flowid: string, chatId: string) => Promise<void>;
|
||||
loadMoreHistoryMsg: (flowid: string) => Promise<void>;
|
||||
loadHistoryMsg: (flowid: string, chatId: string, flow_type: string) => Promise<void>;
|
||||
loadMoreHistoryMsg: (flowid: string, flow_type: string) => Promise<void>;
|
||||
destory: () => void;
|
||||
createSendMsg: (inputs: any, inputKey?: string) => void;
|
||||
createWsMsg: (data: any) => void;
|
||||
@@ -81,20 +81,21 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
setShowGuideQuestion(bln: boolean) {
|
||||
set({ showGuideQuestion: bln })
|
||||
},
|
||||
async loadHistoryMsg(flowid, chatId) {
|
||||
const res = await getChatHistory(flowid, chatId, 30, 0)
|
||||
async loadHistoryMsg(flowid, chatId, flow_type) {
|
||||
console.log(111)
|
||||
const res = await getChatHistory(flowid, chatId, 30, 0, flow_type)
|
||||
const msgs = handleHistoryMsg(res)
|
||||
currentChatId = chatId
|
||||
set({ historyEnd: false, messages: msgs.reverse() })
|
||||
},
|
||||
async loadMoreHistoryMsg(flowid) {
|
||||
async loadMoreHistoryMsg(flowid, flow_type) {
|
||||
if (get().running) return // 会话进行中禁止加载more历史
|
||||
if (get().historyEnd) return // 没有更多历史纪录
|
||||
const chatId = get().chatId
|
||||
const prevMsgs = get().messages
|
||||
// 最后一条消息id不存在,忽略 loadmore
|
||||
if (!prevMsgs[0]?.id) return
|
||||
const res = await getChatHistory(flowid, chatId, 10, prevMsgs[0]?.id || 0)
|
||||
const res = await getChatHistory(flowid, chatId, 10, prevMsgs[0]?.id || 0, flow_type)
|
||||
// 过滤非同一会话消息
|
||||
if (res[0]?.chat_id !== currentChatId) {
|
||||
return console.warn('loadMoreHistoryMsg chatId not match, ignore')
|
||||
|
||||
@@ -5,10 +5,20 @@ import { 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";
|
||||
import CardComponent from "../cardComponent";
|
||||
import CardComponent, { TitleIconBg } from "../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";
|
||||
|
||||
export default function SkillChatSheet({ children, onSelect }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -32,19 +42,71 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase()))
|
||||
}, [keyword])
|
||||
|
||||
const render = (item: any) => (
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox relative`} onClick={() => { onSelect(item); setOpen(false) }}>
|
||||
{/* <Avatar size={24} src={item.favicon} style={{ flex: 'none' }} /> */}
|
||||
{/* <Flexbox>
|
||||
<div style={{ fontSize: 15, fontWeight: 600 }}>{item.name}</div>
|
||||
<div style={{ opacity: 0.6 }}>{item.name}</div>
|
||||
</Flexbox> */}
|
||||
{/* <Card key={item.id} className="w-[300px] overflow-hidden cursor-pointer" onClick={() => onSelect(item)}>
|
||||
<CardHeader>
|
||||
<CardTitle className=" flex items-center gap-2">
|
||||
<div className={"rounded-full w-[30px] h-[30px] " + gradients[parseInt(item.id, 16) % gradients.length]}></div>
|
||||
<span>{item.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="">{item.description}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card> */}
|
||||
<div className="npcInfoItemBg">
|
||||
<span>
|
||||
<span>
|
||||
<div>
|
||||
{/* <img src={robot} className="w-[160px]" alt=""/> */}
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[160px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[160px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[160px]" alt=""/>}
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{/* {(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[42px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[42px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[42px]" alt=""/>} */}
|
||||
{/* <img src={robot} className="w-[42px]" alt=""/> */}
|
||||
<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>绘画类</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 <Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
{children}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:min-w-[966px] bg-gray-100">
|
||||
<SheetContent className="sm:min-w-[1000px] bg-[#1A1A1A]">
|
||||
<div className="flex h-full" onClick={e => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<SheetTitle>{t('chat.dialogueSelection')}</SheetTitle>
|
||||
<SheetDescription>{t('chat.chooseSkillOrAssistant')}</SheetDescription>
|
||||
<SearchInput value={keyword} placeholder={t('chat.search')} className="my-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="flex-1 min-w-[696px] bg-[#fff] p-5 pt-12 h-full flex flex-wrap gap-1.5 overflow-y-auto scrollbar-hide content-start">
|
||||
{
|
||||
<div className="w-fit p-6">
|
||||
<SheetTitle>选择对话</SheetTitle>
|
||||
<SheetDescription className="text-[#999999]">选择一个您想使用的上线NPC或能力</SheetDescription>
|
||||
<SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
</div>
|
||||
<div className="min-w-[696px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
|
||||
{/* {
|
||||
options.length ? options.map((flow, i) => (
|
||||
<CardComponent key={i}
|
||||
id={i + 1}
|
||||
@@ -55,7 +117,7 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
icon={flow.flow_type === 'flow' ? SkillIcon : AssistantIcon}
|
||||
footer={
|
||||
<Badge className={`absolute right-0 bottom-0 rounded-none rounded-br-md ${flow.flow_type === 'flow' && 'bg-gray-950'}`}>
|
||||
{flow.flow_type === 'flow' ? '技能' : '助手'}
|
||||
{flow.flow_type === 'flow' ? '技能' : 'NPC'}
|
||||
</Badge>
|
||||
}
|
||||
onClick={() => { onSelect(flow); setOpen(false) }}
|
||||
@@ -64,7 +126,8 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
<p className="text-sm text-muted-foreground mb-3">{t('build.empty')}</p>
|
||||
<Button className="w-[200px]" onClick={() => navigate('/build/assist')}>{t('build.onlineSA')}</Button>
|
||||
</div>
|
||||
}
|
||||
} */}
|
||||
<SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
|
||||
@@ -19,6 +19,7 @@ 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 borderR from "../../../assets/npc/border-r.png";
|
||||
|
||||
export default function SkillSheet({ select, children, onSelect }) {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
@@ -107,9 +108,12 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>{children}</SheetTrigger>
|
||||
<SheetContent className="bg-[#1A1A1A] sm:min-w-[966px]">
|
||||
<SheetContent className="bg-[#1A1A1A] sm:min-w-[1000px]">
|
||||
<div className="flex h-full" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-[270px] p-6">
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<SheetTitle>{t("build.addSkill")}</SheetTitle>
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
|
||||
@@ -11,6 +11,7 @@ import sousuo from "../../../assets/npc/sousuo.png";
|
||||
import gongjuAdd from "../../../assets/npc/gongjuAdd.png";
|
||||
import gongjuIcon from "../../../assets/npc/gongjuIcon.png";
|
||||
import gongjuIcon1 from "../../../assets/npc/gongjuIcon1.png";
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
|
||||
export default function ToolsSheet({ select, onSelect, children }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -38,8 +39,11 @@ export default function ToolsSheet({ select, onSelect, children }) {
|
||||
<SheetTrigger asChild>
|
||||
{children}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-[#121212]">
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-[#1a1a1a]">
|
||||
<div className="flex h-full" onClick={e => e.stopPropagation()}>
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="w-fit p-6">
|
||||
<SheetTitle>{t('build.addTool')}</SheetTitle>
|
||||
<div className="relative mt-[14px]">
|
||||
@@ -67,7 +71,7 @@ export default function ToolsSheet({ select, onSelect, children }) {
|
||||
>
|
||||
{/* <PersonIcon /> */}
|
||||
{type === "default" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
<span className="ml-[8px] text-[#999999]">内置工具</span>
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "default" && "text-[#FFD025]"}`}>内置工具</span>
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 mt-1 ${type === 'custom' && 'bg-[#2A271D] text-[#FFD54C]'}`}
|
||||
@@ -75,7 +79,7 @@ export default function ToolsSheet({ select, onSelect, children }) {
|
||||
>
|
||||
{type === "custom" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <StarFilledIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">自定义工具</span>
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "custom" && "text-[#FFD025]"}`}>自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,14 +14,14 @@ export default function ActionButton({
|
||||
...props
|
||||
}) {
|
||||
|
||||
return <div className="flex items-center">
|
||||
return <div className="flex items-center bg-[#1A1A1A]">
|
||||
<>
|
||||
{buttonTipContent ? <TooltipProvider>
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant={variant} className={`rounded-r-none ${className}`} {...props}>{children}</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-[#fff] text-gray-800">
|
||||
<TooltipContent className="bg-[#1A1A1A] text-[#CCCCCC]">
|
||||
{buttonTipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -34,10 +34,11 @@ export default function ActionButton({
|
||||
<Button
|
||||
size="icon"
|
||||
variant={variant}
|
||||
className="rounded-l-none ml-[1px] [&[data-state=open]>svg]:rotate-180"
|
||||
className=" ml-[1px] [&[data-state=open]>svg]:rotate-180 bg-[#262626] w-[18px] h-[18px] mr-[5px]"
|
||||
style={{borderRadius:"3px"}}
|
||||
><CaretDownIcon /></Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-0" align={align}>
|
||||
<PopoverContent className="w-80 p-0 bg-[#1A1A1A]" align={align}>
|
||||
{dropDown}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -115,7 +115,7 @@ const InputList = React.forwardRef<HTMLDivElement, InputProps & {
|
||||
<Input
|
||||
key={item.id}
|
||||
defaultValue={item.value}
|
||||
className={cname('pr-8', inputClassName)}
|
||||
className={cname('pr-8 npcInput2', inputClassName)}
|
||||
placeholder={props.placeholder || ''}
|
||||
onChange={(e) => handleChange(e.target.value, item.id, index)}
|
||||
onInput={(e) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#1a1a1a] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
|
||||
"group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#1a1a1a] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -40,7 +40,7 @@ const sheetVariants = cva(
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
"inset-y-0 right-0 h-full w-3/4 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -99,7 +99,7 @@ const ToastTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cname("text-sm font-semibold text-[#fff] [&+div]:text-xs group-[.info]:text-[#fff] group-[.success]:text-[#fff] group-[.warning]:text-[#fff] group-[.error]:text-[#fff]", className)}
|
||||
className={cname("text-sm font-semibold text-[#fff] [&+div]:text-xs group-[.info]:text-[#024FE5] group-[.success]:text-[#fff] group-[.warning]:text-[#EA991F] group-[.error]:text-[#D8341E]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -22,8 +22,8 @@ export const getAssistantsApi = async (page, limit, name): Promise<AssistantItem
|
||||
};
|
||||
|
||||
// 创建助手
|
||||
export const createAssistantsApi = async (name, prompt) => {
|
||||
return await axios.post(`/api/v1/assistant`, { name, prompt, logo: '' })
|
||||
export const createAssistantsApi = async (name, prompt, avatar_img, avatar_color) => {
|
||||
return await axios.post(`/api/v1/assistant`, { name, prompt, logo: '', avatar_img, avatar_color })
|
||||
};
|
||||
|
||||
// 获取助手详情
|
||||
|
||||
@@ -203,7 +203,9 @@ export async function updateFlowApi(
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
guide_word: updatedFlow.guide_word
|
||||
guide_word: updatedFlow.guide_word,
|
||||
avatar_img: updatedFlow.avatar_img,
|
||||
avatar_color: updatedFlow.avatar_color,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -107,9 +107,9 @@ export function updateTempApi(temp_id, data) {
|
||||
* 获取知识库列表
|
||||
*
|
||||
*/
|
||||
export async function readFileLibDatabase(page = 1, pageSize = 40, name = '', parentId = '',type = '') {
|
||||
export async function readFileLibDatabase(page = 1, pageSize = 40, name = '') {
|
||||
try {
|
||||
const response: { data: any[], total: number } = await axios.get(`/api/v1/knowledge/?page_num=${page}&page_size=${pageSize}&name=${name}&parent_id=${parentId}&type=${type}`);
|
||||
const response: { data: any[], total: number } = await axios.get(`/api/v1/knowledge/?page_num=${page}&page_size=${pageSize}&name=${name}`);
|
||||
// const { data, total } = response
|
||||
return response;
|
||||
} catch (error) {
|
||||
@@ -300,8 +300,38 @@ export const deleteChatApi = (chatId) => {
|
||||
* @param id flow_id chat_id - .
|
||||
* @returns {Promise<any>} his data.
|
||||
*/
|
||||
export async function getChatHistory(flowId: string, chatId: string, pageSize: number, id?: number): Promise<any[]> {
|
||||
return await axios.get(`/api/v1/chat/history?flow_id=${flowId}&chat_id=${chatId}&page_size=${pageSize}&id=${id || ''}`);
|
||||
export interface MessageDB {
|
||||
/** 场景 */
|
||||
category: string;
|
||||
chat_id: string;
|
||||
create_time: string;
|
||||
extra: string;
|
||||
/** 文件列表 */
|
||||
files: string;
|
||||
flow_id: string;
|
||||
id: number;
|
||||
/** 日志 */
|
||||
intermediate_steps: string;
|
||||
/** 机器人回复 */
|
||||
is_bot: boolean;
|
||||
/** 已点赞 */
|
||||
liked: number;
|
||||
/** 消息内容 */
|
||||
message: string;
|
||||
receiver: null;
|
||||
remark: null;
|
||||
sender: string;
|
||||
solved: number;
|
||||
/** 有溯源 */
|
||||
source: number;
|
||||
type: string;
|
||||
update_time: string;
|
||||
user_id: number;
|
||||
flow_type: string;
|
||||
}
|
||||
|
||||
export async function getChatHistory(flowId: string, chatId: string, pageSize: number, id?: number, flow_type: string): Promise<MessageDB[]> {
|
||||
return await axios.get(`/api/v1/chat/history?flow_id=${flowId}&chat_id=${chatId}&page_size=${pageSize}&id=${id || ''}&flow_type=${flow_type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,9 +365,11 @@ export async function getVersion() {
|
||||
*
|
||||
*/
|
||||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
flowId: string,
|
||||
versionId?: number
|
||||
): Promise<BuildStatusTypeAPI> {
|
||||
return await axios.get(`/api/v1/build/${flowId}/status`);
|
||||
const qstr = versionId ? `?version_id=${versionId}` : "";
|
||||
return await axios.get(`/api/v1/build/${flowId}/status${qstr}`);
|
||||
}
|
||||
|
||||
//docs for postbuildinit
|
||||
@@ -347,11 +379,14 @@ export async function getBuildStatus(
|
||||
* @returns {Promise<InitTypeAPI>} A promise that resolves to an AxiosResponse containing the build status.
|
||||
*
|
||||
*/
|
||||
export async function postBuildInit(
|
||||
flow: FlowType,
|
||||
export async function postBuildInit(data: {
|
||||
flow: FlowType
|
||||
chatId?: string
|
||||
): Promise<any> {
|
||||
return await axios.post(`/api/v1/build/init/${flow.id}`, chatId ? { chat_id: chatId } : flow);
|
||||
versionId?: number
|
||||
}): Promise<any> {
|
||||
const { flow, chatId, versionId } = data;
|
||||
const qstr = versionId ? `?version_id=${versionId}` : ''
|
||||
return await axios.post(`/api/v1/build/init/${flow.id}${qstr}`, chatId ? { chat_id: chatId } : flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toast } from "@/components/bs-ui/toast/use-toast";
|
||||
import axios from "axios";
|
||||
|
||||
import i18next from "i18next";
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
const customAxios = axios.create({
|
||||
// 配置
|
||||
});
|
||||
@@ -23,7 +23,12 @@ customAxios.interceptors.response.use(function (response) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
// app 弹窗
|
||||
window.errorAlerts([error.message])
|
||||
toast({
|
||||
title: `${i18next.t('prompt')}`,
|
||||
variant: 'error',
|
||||
description: error.message
|
||||
})
|
||||
// window.errorAlerts([error.message])
|
||||
return Promise.reject(null);
|
||||
})
|
||||
|
||||
@@ -38,7 +43,11 @@ export function captureAndAlertRequestErrorHoc(apiFunc, iocFunc?) {
|
||||
console.log('error :>> ', error);
|
||||
iocFunc?.(error)
|
||||
// 弹窗
|
||||
window.errorAlerts([error])
|
||||
toast({
|
||||
title: `${i18next.t('prompt')}`,
|
||||
variant: 'error',
|
||||
description: typeof error === 'string' ? error : JSON.stringify(error)
|
||||
})
|
||||
console.error('逻辑异常 :>> ', error);
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function MainLayout() {
|
||||
})
|
||||
}
|
||||
return <div className="flex">
|
||||
<div className="bg-white h-screen w-[84px] border-r dark:shadow-slate-700 relative text-center">
|
||||
<div className="bg-white h-screen w-[84px] dark:shadow-slate-700 relative text-center">
|
||||
<Link className="inline-block mt-[21px]" to='/'><img src={login} className="w-[42px] h-[42px]" alt="" /></Link>
|
||||
{/* <h1 className="text-white font-bold text-xl text-center">{t('title')}</h1> */}
|
||||
<nav className="mt-8">
|
||||
@@ -134,7 +134,7 @@ export default function MainLayout() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex-1 overflow-hidden xinDuiHua-box">
|
||||
<ErrorBoundary
|
||||
onReset={() => window.location.href = window.location.href}
|
||||
FallbackComponent={CrashErrorComponent}
|
||||
|
||||
@@ -77,21 +77,35 @@ export default function chatShare() {
|
||||
setChatId(generateUUID(32))
|
||||
})
|
||||
}, [flowId])
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
|
||||
const wsUrl = useMemo(() => {
|
||||
const params = [];
|
||||
|
||||
if (libId) params.push(`knowledge_id=${libId}`);
|
||||
if (tweak) params.push(`tweak=${tweak}`);
|
||||
|
||||
const paramStr = params.length > 0 ? `${params.join('&')}` : '';
|
||||
|
||||
return `/api/v2/chat/ws/${flowId}?type=L1&${paramStr}`
|
||||
}, [libId, tweak])
|
||||
|
||||
const [data] = useState<any>({ id: flowId, chatId: generateUUID(32), type: 'flow' })
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
if (!flowId) return <div>请选择技能</div>
|
||||
|
||||
return <div className="chatShare">
|
||||
@@ -127,13 +141,14 @@ export default function chatShare() {
|
||||
</div>
|
||||
</div> */}
|
||||
{/* chat */}
|
||||
{flow
|
||||
{/* {flow
|
||||
? <div className="flex-1 chat-box relative">
|
||||
{flow && <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} />}
|
||||
</div>
|
||||
:<div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>}
|
||||
</div>} */}
|
||||
<ChatPanne customWsHost={wsUrl} data={data} />
|
||||
</div>
|
||||
{/* {flow ? <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} /> : null} */}
|
||||
{/* 选择对话技能 */}
|
||||
|
||||
@@ -235,23 +235,24 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
// if (chat.isSend) return chat.files.length ? <>
|
||||
// 发送消息
|
||||
if (chat.isSend) return <div className="flex flex-col items-end">
|
||||
<div className="flex items-center avatarZk">
|
||||
<p className="mr-[20px] text-[14px]">{userName}</p>
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <p className="mr-[20px] text-[14px]">{userName}</p> */}
|
||||
<img src={robotU} className="w-[50px]" alt=""/>
|
||||
</div>
|
||||
<div className="mt-[10px] mr-[60px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* 文件 */ }
|
||||
// <div className="chat chat-end">
|
||||
@@ -275,11 +276,12 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>}
|
||||
<p className="ml-[20px] text-[14px]">{userName}</p>
|
||||
|
||||
</div>
|
||||
{chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>}
|
||||
<Card className={`my-2 w-[200px] relative ${chat.files[0]?.file_url && 'cursor-pointer'}`} onClick={() => handleDownloadFile(chat.files[0])}>
|
||||
@@ -295,33 +297,34 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>}
|
||||
<p className="ml-[20px] text-[14px]">{userName}</p>
|
||||
{/* <p className="ml-[20px] text-[14px]">{userName}</p> */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] ml-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* {chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>} */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ml-[60px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
|
||||
{chat.source !== SourceType.NONE && chat.end && sourceContent(chat.source)}
|
||||
</div>
|
||||
};
|
||||
|
||||
948
src/pages/ChatAppPage/components/ChatPanne1 copy.tsx
Normal file
@@ -0,0 +1,948 @@
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { ClipboardList, FileInput, FileText, Send, StopCircle } from "lucide-react";
|
||||
import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShadTooltip from "../../../components/ShadTooltipComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { getChatHistory, postBuildInit, postValidatePrompt } from "../../../controllers/API";
|
||||
import { Variable } from "../../../controllers/API/flow";
|
||||
import { sendAllProps } from "../../../types/api";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { FlowType, NodeType } from "../../../types/flow";
|
||||
import { validateNode } from "../../../utils";
|
||||
import { ChatMessage } from "./ChatMessage";
|
||||
import ChatReportForm from "./ChatReportForm";
|
||||
import ResouceModal from "./ResouceModal";
|
||||
import ThumbsMessage from "./ThumbsMessage";
|
||||
import NpcInfo from "./NpcInfo";
|
||||
import Quote from "./quote";
|
||||
import { locationContext } from "../../../contexts/locationContext";
|
||||
import CompleteRecords from "./completeRecords";
|
||||
import DataContent from "./dataContent";
|
||||
import DialogueDetails from "./dialogueDetails";
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import duihuaGengduo from "../../../assets/chat/duihua-gengduo.png";
|
||||
import duihuaSend from "../../../assets/chat/duihua-send.png";
|
||||
|
||||
interface Iprops {
|
||||
chatId: string
|
||||
flow: FlowType
|
||||
queryString?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export default forwardRef(function ChatPanne({ chatId, flow, queryString, version = 'v1' }: Iprops,ref) {
|
||||
const { t } = useTranslation()
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const { isRoom, isForm, isReport, checkPrompt } = useFlowState(flow)
|
||||
|
||||
// build
|
||||
const build = useBuild(flow, chatId)
|
||||
// 消息列表
|
||||
const { messages, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll } = useMessages(chatId, flow)
|
||||
// ws通信
|
||||
const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
|
||||
// 停止状态
|
||||
const [isStop, setIsStop] = useState(true)
|
||||
// 输入框状态
|
||||
const { inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight } = useInputState({ flow, chatId, chating, messages, isForm, isReport })
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
// npc信息
|
||||
const [isNpcInfo, setIsNpcInfo] = useState(false)
|
||||
|
||||
// 知识库引用信息
|
||||
const [isQuote, setIsQuote] = useState(false)
|
||||
|
||||
// 完整对话记录
|
||||
const [isCompleteRecords, setIsCompleteRecords] = useState(false)
|
||||
|
||||
// 数据内容
|
||||
const [isDataContent, setIsDataContent] = useState(false)
|
||||
|
||||
// 对话详情
|
||||
const [isDialogueDetails, setIsDialogueDetails] = useState(false)
|
||||
|
||||
// 开始构建&切换初始化会话
|
||||
const initChat = async () => {
|
||||
await checkPrompt(flow)
|
||||
await build()
|
||||
const historyData = version === 'v1' ? await loadHistory() : (initGuide(), [])
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
// 第一条消息,用来初始化会话
|
||||
sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
inputs: {},
|
||||
flow_id: flow.id,
|
||||
chat_id: chatId
|
||||
})
|
||||
|
||||
changeHistoryByScroll.current = false
|
||||
// 自动聚焦
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 500);
|
||||
|
||||
const isNewChat = historyData.length === 0 || historyData[0].id === 9999
|
||||
setFormShow(isNewChat && isForm)
|
||||
}
|
||||
useEffect(() => {
|
||||
initChat()
|
||||
}, [flow])
|
||||
|
||||
// sendmsg user name
|
||||
const sendUserName = useMemo(() => {
|
||||
const node = flow.data.nodes.find(el => el.data.type === 'AutoGenUser')
|
||||
return node?.data.node.template['name'].value || ''
|
||||
}, [flow])
|
||||
|
||||
const handleSend = async () => {
|
||||
const msg = inputRef.current?.value
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ''
|
||||
inputRef.current.style.height = 'auto'
|
||||
}
|
||||
setInputEmpty(true)
|
||||
}, 100);
|
||||
|
||||
if (msg.trim() === '') return
|
||||
|
||||
setInputState({ lock: true, errorMsg: '' });
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: msg },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
await checkReLinkWs(async () => {
|
||||
// await build()
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
})
|
||||
|
||||
const chatInfo = {
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
inputs: { ...input, [inputKey]: msg }
|
||||
}
|
||||
// @ts-ignore
|
||||
isRoom && chating ? sendAll({ action: "continue", ...chatInfo })
|
||||
: sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
...chatInfo
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 报表请求
|
||||
const sendReport = (items: Variable[], str) => {
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: str },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
const data = items.map(item => ({
|
||||
id: item.nodeId,
|
||||
name: item.name,
|
||||
file_path: item.type === 'file' ? item.value : '',
|
||||
value: item.type === 'file' ? '' : item.value
|
||||
}))
|
||||
|
||||
setIsStop(false)
|
||||
setFormShow(false)
|
||||
|
||||
sendAll({
|
||||
inputs: {
|
||||
...input,
|
||||
[inputKey]: str,
|
||||
data
|
||||
},
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 溯源
|
||||
const [souce, setSouce] = useState<ChatMessageType>(null)
|
||||
|
||||
const thumbRef = useRef(null)
|
||||
|
||||
return <div className="overflow-hidden relative duihua-chat">
|
||||
<div className="absolute duihua-chat-top">
|
||||
<div>
|
||||
{/* <img src={robot} alt=""/> */}
|
||||
{(flow.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || flow.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{flow.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} alt=""/>}
|
||||
{(flow.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && flow.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && flow.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>}
|
||||
<p>{flow.name}</p>
|
||||
{/* <div className="cursor-pointer">3条记录</div> */}
|
||||
{/* <div className="cursor-pointer">模型列表</div> */}
|
||||
</div>
|
||||
<div className="cursor-pointer" onClick={() => setIsNpcInfo(!isNpcInfo)}>
|
||||
{/* <img src={duihuaGengduo} alt=""/> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="chata mt-[70px]" style={{ height: 'calc(100% - 170px)' }}>
|
||||
{/* 会话记录 */}
|
||||
<div ref={messagesRef} className={`chat-panne h-full overflow-y-scroll no-scrollbar px-[35px] ${isRoom || isReport ? 'pb-0' : 'pb-0'}`}>
|
||||
{
|
||||
messages.map((c, i) => <ChatMessage
|
||||
key={c.id || i}
|
||||
userName={sendUserName}
|
||||
chat={c}
|
||||
disabledReSend={inputDisabled}
|
||||
showSearch={!!appConfig.dialogQuickSearch}
|
||||
onSource={() => setSouce(c)}
|
||||
onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }}
|
||||
onReSend={(msg) => {
|
||||
inputRef.current.value = msg
|
||||
handleSend()
|
||||
}}
|
||||
onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }}
|
||||
onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))}
|
||||
></ChatMessage>)
|
||||
}
|
||||
</div>
|
||||
{/* 输入框 */}
|
||||
{version != "v3" && <div className="absolute w-full bottom-0 duihua-input-box pb-[40px]">
|
||||
{/* <div className={`relative duihua-input
|
||||
${inputDisabled && 'bg-gray-200 dark:bg-gray-600'}`}> */}
|
||||
<div className={`relative duihua-input`}>
|
||||
<textarea id='input'
|
||||
ref={inputRef}
|
||||
disabled={inputDisabled} rows={1}
|
||||
className={`w-full resize-none border-none bg-transparent outline-none max-h-[160px]`}
|
||||
placeholder={ (inputDisabled ? "当前处于回复中或不支持输入状态" : t('chat.inputPlaceholder'))}
|
||||
onInput={handleTextAreaHeight}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) handleSend()
|
||||
}}></textarea>
|
||||
<div className="absolute right-0 bottom-0 w-[84px] duihua-input-btn cursor-pointer">
|
||||
{/* <ShadTooltip content={t('chat.sendTooltip')}>
|
||||
<button disabled={inputEmpty || inputDisabled} className=" disabled:text-gray-400" onClick={handleSend}><Send /></button>
|
||||
</ShadTooltip> */}
|
||||
<div className="duihua-input-btn-send">
|
||||
<img src={duihuaSend} onClick={handleSend} alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
{inputState.errorMsg && <div className="absolute top-0 left-0 w-full h-full text-center align-middle pt-4" style={{background:"#2E1212",color:"#FF6060"}}>{inputState.errorMsg}</div>}
|
||||
</div>
|
||||
<p className="mb-2 text-center text-gray-400 text-sm">{appConfig.dialogTips}</p>
|
||||
</div>}
|
||||
</div>
|
||||
{(isRoom || isReport) && <div className=" absolute w-full flex justify-center bottom-32 pointer-events-none">
|
||||
<Button className="rounded-full pointer-events-auto" variant="outline" disabled={isStop} onClick={() => { setIsStop(true); stop(); }}><StopCircle className="mr-2" />Stop</Button>
|
||||
</div>}
|
||||
{/* 源文件类型 */}
|
||||
<ResouceModal chatId={chatId} open={!!souce} data={souce} setOpen={() => setSouce(null)}></ResouceModal>
|
||||
{/* 表单 */}
|
||||
{isForm && formShow && <ChatReportForm flow={flow} onStart={sendReport} />}
|
||||
{/* 踩 反馈 */}
|
||||
<ThumbsMessage ref={thumbRef}></ThumbsMessage>
|
||||
|
||||
<NpcInfo isNpcInfo={isNpcInfo} setIsNpcInfo={setIsNpcInfo}></NpcInfo>
|
||||
|
||||
<Quote isQuote={isQuote} setIsQuote={setIsQuote}></Quote>
|
||||
|
||||
<CompleteRecords isCompleteRecords={isCompleteRecords} setIsCompleteRecords={setIsCompleteRecords} id={undefined}></CompleteRecords>
|
||||
|
||||
<DataContent isDataContent={isDataContent} setIsDataContent={setIsDataContent} id={undefined}></DataContent>
|
||||
|
||||
<DialogueDetails isDialogueDetails={isDialogueDetails} setIsDialogueDetails={setIsDialogueDetails}></DialogueDetails>
|
||||
</div>
|
||||
});
|
||||
/**
|
||||
* 输入框状态
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useInputState = ({ flow, chatId, chating, messages, isForm, isReport }) => {
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const [inputState, setInputState] = useState({
|
||||
lock: false,
|
||||
errorMsg: ''
|
||||
})
|
||||
// 输入问答
|
||||
const inputRef = useRef(null)
|
||||
useEffect(() => {
|
||||
!chating && setTimeout(() => {
|
||||
// 对话结束自动聚焦
|
||||
inputRef.current?.focus()
|
||||
}, 1000);
|
||||
}, [chating])
|
||||
// input 滚动
|
||||
const [inputEmpty, setInputEmpty] = useState(true)
|
||||
useEffect(() => {
|
||||
setInputEmpty(true)
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
}, [chatId])
|
||||
|
||||
// 获取上传file input
|
||||
const fileInputs = useMemo(() => {
|
||||
return tabsState[flow.id]?.formKeysData?.input_keys?.filter((input: any) => input.type === 'file')
|
||||
}, [tabsState, flow])
|
||||
|
||||
const handleTextAreaHeight = (e) => {
|
||||
const textarea = e.target
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = textarea.scrollHeight + 'px'
|
||||
setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
// input disabled
|
||||
const inputDisabled = useMemo(() => {
|
||||
return inputState.lock
|
||||
// 表单 && 没回话或只有一个引导词
|
||||
|| (isForm && (messages.length === 0 || (messages.length === 1 && messages[0].id === 9999)))
|
||||
|| isReport
|
||||
}, [inputState, fileInputs, isReport])
|
||||
|
||||
// 表单收起
|
||||
const [formShow, setFormShow] = useState(true)
|
||||
return {
|
||||
inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flow state
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useFlowState = (flow: FlowType) => {
|
||||
const flowSate = useMemo(() => {
|
||||
// 是否群聊
|
||||
const isRoom = !!flow.data?.nodes.find(node => node.data.type === "AutoGenChain")
|
||||
// 是否展示表单
|
||||
const isForm = !!flow.data?.nodes.find(node => ["VariableNode", "InputFileNode"].includes(node.data.type))
|
||||
// 是否报表
|
||||
const isReport = !!flow.data?.nodes.find(node => "Report" === node.data.type)
|
||||
return { isRoom, isForm, isReport }
|
||||
}, [flow])
|
||||
|
||||
// propmt类型补充自定义字段
|
||||
const checkPrompt = async (_flow) => {
|
||||
const params = _flow.data.nodes.map(node => {
|
||||
const temps = []
|
||||
const temp = node.data.node.template
|
||||
Object.keys(temp).map(key => {
|
||||
const { type, value } = temp[key]
|
||||
if (type === 'prompt' && !!value) !temps.length && temps.push({ name: key, template: value, data: node.data })
|
||||
})
|
||||
return temps
|
||||
}).flat()
|
||||
|
||||
const promises = params.map(param => {
|
||||
return postValidatePrompt(param.name, param.template, param.data.node).then(res => {
|
||||
if (res) param.data.node = res.frontend_node
|
||||
})
|
||||
})
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
return { ...flowSate, checkPrompt }
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表模块
|
||||
* 翻页、追加、历史
|
||||
* @returns
|
||||
*/
|
||||
const useMessages = (chatId, flow) => {
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const lastIdRef = useRef(0)
|
||||
// 控制开启自动随消息滚动(临时方案)
|
||||
const changeHistoryByScroll = useRef(false)
|
||||
|
||||
const loadIdRef = useRef('') // 记录最后一个加载的 chatId
|
||||
// 获取聊天记录
|
||||
const loadHistory = async (lastId?: number) => {
|
||||
loadIdRef.current = chatId
|
||||
|
||||
const res = await getChatHistory(flow.id, chatId, lastId ? 10 : 30, lastId)
|
||||
const hisData = res.map(item => {
|
||||
// let count = 0
|
||||
let { message, files, is_bot, intermediate_steps, ...other } = item
|
||||
try {
|
||||
message = message && message[0] === '{' ? JSON.parse(message.replace(/([\t\n"])/g, '\\$1').replace(/'/g, '"')) : message || ''
|
||||
} catch (e) {
|
||||
// 未考虑的情况暂不处理
|
||||
}
|
||||
return {
|
||||
...other,
|
||||
chatKey: typeof message === 'string' ? undefined : Object.keys(message)[0],
|
||||
end: true,
|
||||
files: files ? JSON.parse(files) : [],
|
||||
isSend: !is_bot,
|
||||
message,
|
||||
thought: intermediate_steps,
|
||||
noAccess: true
|
||||
}
|
||||
})
|
||||
lastIdRef.current = hisData[hisData.length - 1]?.id || lastIdRef.current || 0 // 记录最后一个id
|
||||
|
||||
let historyData = []
|
||||
if (lastId) {
|
||||
historyData = [...hisData.reverse(), ...chatHistory]
|
||||
} else if (loadIdRef.current === chatId) { // 保证同一会话
|
||||
historyData = hisData.reverse()
|
||||
}
|
||||
setChatHistory(historyData)
|
||||
const pageSize = historyData.length < 30 ? 30 : 10 // 先偷懒
|
||||
if (hisData.length < pageSize) initGuide()
|
||||
return historyData
|
||||
}
|
||||
|
||||
const loadLock = useRef(false)
|
||||
const currentIdRef = useRef(0)
|
||||
const loadNextPage = async () => {
|
||||
if (loadLock.current) return
|
||||
if (currentIdRef.current === lastIdRef.current) return // 最后一个相同表示聊天记录已到顶
|
||||
loadLock.current = true
|
||||
currentIdRef.current = lastIdRef.current
|
||||
changeHistoryByScroll.current = true
|
||||
await loadHistory(currentIdRef.current)
|
||||
loadLock.current = false
|
||||
// 滚动 hack TODO 滚动翻页设计
|
||||
setTimeout(() => {
|
||||
changeHistoryByScroll.current = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const initGuide = () => {
|
||||
const guideMsg = {
|
||||
"category": "system",
|
||||
"chat_id": chatId,
|
||||
"end": true,
|
||||
"create_time": "",
|
||||
"extra": "{}",
|
||||
"files": [],
|
||||
"flow_id": flow.id,
|
||||
"id": 9999,
|
||||
"thought": flow.guide_word,
|
||||
"is_bot": true,
|
||||
"liked": 0,
|
||||
"message": '',
|
||||
"receiver": null,
|
||||
"remark": null,
|
||||
"sender": "",
|
||||
"solved": 0,
|
||||
isSend: false,
|
||||
"source": 0,
|
||||
"type": "end",
|
||||
"update_time": "",
|
||||
noAccess: true,
|
||||
"user_id": 0
|
||||
}
|
||||
flow.guide_word && setChatHistory((chatHistory) =>
|
||||
chatHistory[0]?.id === 9999 ? chatHistory : [guideMsg, ...chatHistory]
|
||||
)
|
||||
}
|
||||
|
||||
// 消息滚动
|
||||
const messagesRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (messagesRef.current) { // 滚动加载不触发
|
||||
// if (messagesRef.current && !changeHistoryByScroll.current) { // 滚动加载不触发
|
||||
console.log(1,messagesRef,changeHistoryByScroll)
|
||||
setTimeout(() => {
|
||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
||||
}, 100);
|
||||
}
|
||||
}, [chatHistory, changeHistoryByScroll]);
|
||||
|
||||
// 消息滚动加载
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
if (messagesRef.current.scrollTop <= 30) {
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
messagesRef.current?.addEventListener('scroll', handleScroll);
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, chatHistory, chatId]);
|
||||
|
||||
return {
|
||||
messages: chatHistory, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 通信
|
||||
* 建立连接、重连、断开、接收、发送
|
||||
* @returns
|
||||
*/
|
||||
const useWebsocket = (chatId, flow, setChatHistory, queryString, version) => {
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
// 接收ws状态
|
||||
const [begin, setBegin] = useState(false)
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
const chatIdRef = useRef(chatId);
|
||||
useEffect(() => {
|
||||
chatIdRef.current = chatId;
|
||||
}, [chatId])
|
||||
|
||||
function heartbeat() {
|
||||
if (!ws.current) return;
|
||||
if (ws.current.readyState !== 1) return;
|
||||
ws.current.send("heartbeat");
|
||||
setTimeout(heartbeat, 30000);
|
||||
}
|
||||
|
||||
function getWebSocketUrl(flowId, isDevelopment = false) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
|
||||
const isSecureProtocol = window.location.protocol === "https:";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = appConfig.websocketHost || window.location.host // isDevelopment ? "localhost:7860" : window.location.host;
|
||||
const chatEndpoint = version === 'v1' ? `/api/v1/chat/${flowId}?type=L1&chat_id=${chatId}&t=${token}`
|
||||
: `/api/v2/chat/ws/${flowId}?type=L1&chat_id=${chatId}${queryString}&t=${token}`
|
||||
|
||||
return `${webSocketProtocol}://${host}${chatEndpoint}`;
|
||||
}
|
||||
|
||||
const newChatStart = useRef(false) // 处理当前会话上下文丢失,阻止上一次打字机效果
|
||||
// 自动重连次数
|
||||
const tryReLinkCount = useRef(0)
|
||||
const reConnect = (params) => {
|
||||
if (tryReLinkCount.current <= 3) {
|
||||
connectWS(params)
|
||||
tryReLinkCount.current++
|
||||
} else {
|
||||
console.warn('超过最大重试次数 :>> ');
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
tryReLinkCount.current = 0
|
||||
newChatStart.current = true
|
||||
}, [chatId])
|
||||
|
||||
function connectWS(params) {
|
||||
const { setInputState, setIsStop, changeHistoryByScroll } = params
|
||||
if (ws.current) return Promise.resolve('ok');
|
||||
|
||||
// 连接断开重链接
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const urlWs = getWebSocketUrl(
|
||||
flow.id,
|
||||
process.env.NODE_ENV === "development"
|
||||
);
|
||||
const newWs = new WebSocket(urlWs);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
res('ok')
|
||||
// heartbeat()
|
||||
};
|
||||
newWs.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.chat_id !== chatIdRef.current) return
|
||||
console.log('newChatStart.current :>> ', newChatStart.current);
|
||||
|
||||
const errorMsg = data.category === 'error' ? data.intermediate_steps : ''
|
||||
|
||||
if (newChatStart.current) {
|
||||
if (data.type === 'close') {
|
||||
newChatStart.current = false
|
||||
return setInputState({ lock: false, errorMsg })
|
||||
} else {
|
||||
return setInputState({ lock: true, errorMsg })
|
||||
}
|
||||
}
|
||||
// 异常类型处理,提示
|
||||
if (errorMsg) return setInputState({ lock: true, errorMsg })
|
||||
|
||||
handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll });
|
||||
// get chat history
|
||||
// 群聊@自己时,开启input
|
||||
if (data.type === 'end' && data.receiver?.is_self) {
|
||||
setInputState({ lock: false, errorMsg: '' })
|
||||
}
|
||||
};
|
||||
newWs.onclose = (event) => {
|
||||
ws.current = null
|
||||
|
||||
handleOnClose({ event, setIsStop, setInputState });
|
||||
// reConnect(params)
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
ws.current = null
|
||||
|
||||
console.error('链接异常error', ev);
|
||||
setIsStop(true)
|
||||
|
||||
setErrorData({
|
||||
title: `${t('chat.networkError')}:`,
|
||||
list: [
|
||||
t('chat.networkErrorList1'),
|
||||
t('chat.networkErrorList2'),
|
||||
t('chat.networkErrorList3')
|
||||
],
|
||||
});
|
||||
reConnect(params)
|
||||
};
|
||||
ws.current = newWs;
|
||||
console.log('newWs :>> ', newWs);
|
||||
} catch (error) {
|
||||
console.error('创建链接异常', error);
|
||||
rej(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var isStream = false;
|
||||
function handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll }) {
|
||||
if (Array.isArray(data) && data.length) return
|
||||
if (data.type === "begin") {
|
||||
setBegin(true)
|
||||
setIsStop(false)
|
||||
changeHistoryByScroll.current = false
|
||||
}
|
||||
if (data.type === "close") {
|
||||
setBegin(false)
|
||||
setIsStop(true)
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
changeHistoryByScroll.current = true
|
||||
}
|
||||
if (data.type === "start") {
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: false,
|
||||
message: '',
|
||||
chatKey: '',
|
||||
thought: data.intermediate_steps || '',
|
||||
category: data.category || '',
|
||||
files: [],
|
||||
end: false
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
isStream = true;
|
||||
}
|
||||
if (data.type === "stream" && isStream) {
|
||||
updateLastMessage({ str: data.message, thought: data.intermediate_steps });
|
||||
}
|
||||
if (data.type === "end") {
|
||||
updateLastMessage({
|
||||
...data,
|
||||
str: data.message,
|
||||
files: data.files || null,
|
||||
end: true,
|
||||
thought: data.intermediate_steps || '',
|
||||
cate: data.category || '',
|
||||
messageId: data.message_id,
|
||||
noAccess: false,
|
||||
liked: 0
|
||||
});
|
||||
|
||||
isStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastMessage({ str, thought = '', end = false, files = [], cate = '', messageId = 0, source = false, noAccess = false, ...data }: {
|
||||
str: string;
|
||||
messageId?: number
|
||||
thought?: string;
|
||||
cate?: string;
|
||||
end?: boolean;
|
||||
files?: Array<any>;
|
||||
source?: boolean
|
||||
noAccess?: boolean
|
||||
}) {
|
||||
setChatHistory((old) => {
|
||||
const newChats = [...old]
|
||||
// console.log('newchats :>> ', newChats);
|
||||
let chatsLen = newChats.length
|
||||
const prevChat = newChats[chatsLen - 2]
|
||||
// hack 过滤重复最后消息
|
||||
if (end
|
||||
&& str
|
||||
&& chatsLen > 1
|
||||
&& str === prevChat.message
|
||||
// && data.sender === prevChat.sender
|
||||
&& !prevChat.thought) {
|
||||
newChats.splice(chatsLen - 2, 1) // 删上一条
|
||||
chatsLen = newChats.length
|
||||
}
|
||||
// 更新
|
||||
const lastChat = newChats[chatsLen - 1]
|
||||
const newLastChat = {
|
||||
...newChats[chatsLen - 1],
|
||||
...data,
|
||||
id: messageId,
|
||||
message: lastChat.message + str,
|
||||
thought: lastChat.thought + (thought ? `${thought}\n` : ''),
|
||||
files,
|
||||
category: cate,
|
||||
source,
|
||||
noAccess,
|
||||
end
|
||||
}
|
||||
newChats[chatsLen - 1] = newLastChat
|
||||
// start - end 之间没有内容删除load
|
||||
if (end && !(newLastChat.files.length || newLastChat.thought || newLastChat.message)) {
|
||||
newChats.pop()
|
||||
}
|
||||
return newChats;
|
||||
});
|
||||
}
|
||||
|
||||
// 发送ws
|
||||
async function sendAll(data: sendAllProps) {
|
||||
try {
|
||||
if (ws.current) {
|
||||
if (JSON.stringify(data.inputs) !== '{}') {
|
||||
newChatStart.current = false
|
||||
}
|
||||
ws.current.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主动断开
|
||||
function handleOnClose({ event, setIsStop, setInputState }) {
|
||||
console.error('链接手动断开 event :>> ', event);
|
||||
setIsStop(true)
|
||||
setBegin(false)
|
||||
|
||||
if ([1005, 1008].includes(event.code)) {
|
||||
console.warn('即将废弃 :>> ');
|
||||
setInputState({ lock: true, errorMsg: event.reason });
|
||||
} else {
|
||||
if (event.reason) {
|
||||
setErrorData({ title: event.reason });
|
||||
// setChatHistory((old) => {
|
||||
// let newChat = cloneDeep(old);
|
||||
// if (newChat.length) {
|
||||
// newChat[newChat.length - 1].end = true;
|
||||
// }
|
||||
// newChat.push({ end: true, message: `${t('chat.connectionbreakTip')}${event.reason}`, isSend: false, chatKey: '', files: [] });
|
||||
// return newChat
|
||||
// })
|
||||
}
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// destory
|
||||
return () => {
|
||||
// close prev connection
|
||||
if (ws.current) {
|
||||
switch (ws.current.readyState) {
|
||||
case WebSocket.OPEN:
|
||||
console.warn('前端主动关闭1')
|
||||
ws.current.close()
|
||||
; break;
|
||||
case WebSocket.CONNECTING:
|
||||
ws.current.onopen = () => {
|
||||
console.warn('前端主动关闭2')
|
||||
ws.current.close()
|
||||
};
|
||||
}
|
||||
ws.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 检测并重连
|
||||
const checkReLinkWs = async (reConnect) => {
|
||||
if (ws.current) return true
|
||||
// 重连
|
||||
// 上一条加loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = 'loading';
|
||||
return newChat;
|
||||
});
|
||||
await reConnect()
|
||||
// 链接成功
|
||||
// 上一条去loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = '';
|
||||
return newChat;
|
||||
});
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.current.send(JSON.stringify({
|
||||
"action": "stop"
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error stop the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { begin, stop: handleStop, checkReLinkWs, sendAll, connectWS }
|
||||
}
|
||||
|
||||
/**
|
||||
* build flow
|
||||
* 校验每个节点,展示进度及结果;返回input_keys;end_of_stream断开链接
|
||||
* 主要校验节点并设置更新setTabsState的 formKeysData
|
||||
* @returns
|
||||
*/
|
||||
const useBuild = (flow: FlowType, chatId: string) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { setTabsState } = useContext(TabsContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
// SSE 服务端推送
|
||||
async function streamNodeData(flow: FlowType, chatId: string) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const { flowId } = await postBuildInit(flow, chatId);
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
let buildEnd = false
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}?chat_id=${chatId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
eventSource.close(); // 结束关闭链接
|
||||
buildEnd = true
|
||||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
// setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys) {
|
||||
setTabsState((old) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// setProgress(parsedData.progress);
|
||||
validationResults.push(parsedData.valid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
finished = buildEnd // validationResults.length === flow.data.nodes.length;
|
||||
}
|
||||
// Step 4: Return true if all nodes are valid, false otherwise
|
||||
return validationResults.every((result) => result);
|
||||
}
|
||||
|
||||
// 延时器
|
||||
async function enforceMinimumLoadingTime(
|
||||
startTime: number,
|
||||
minimumLoadingTime: number
|
||||
) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = minimumLoadingTime - elapsedTime;
|
||||
|
||||
if (remainingTime > 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuild() {
|
||||
try {
|
||||
const errors = flow.data.nodes.flatMap((n: NodeType) => validateNode(n, flow.data.edges))
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: t('chat.buildError'),
|
||||
list: errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const minimumLoadingTime = 200; // in milliseconds
|
||||
const startTime = Date.now();
|
||||
|
||||
await streamNodeData(flow, chatId);
|
||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime); // 至少等200ms, 再继续(强制最小load时间)
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
return handleBuild
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select";
|
||||
import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Dropdown from "../../../components/dropdownComponent";
|
||||
import InputComponent from "../../../components/inputComponent";
|
||||
import InputFileComponent from "../../../components/inputFileComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
@@ -12,14 +12,23 @@ import { Variable, VariableType, getVariablesApi } from "../../../controllers/AP
|
||||
* @description
|
||||
* 表单项数据由组件的参数信息和单独接口获取的必填信息及排序信息而来。
|
||||
*/
|
||||
export default function ChatReportForm({ flow, onStart }) {
|
||||
const ChatReportForm = forwardRef(({ type = 'chat', vid = 0, flow, onStart }, ref) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => {
|
||||
handleStart()
|
||||
}
|
||||
}));
|
||||
|
||||
// 从 api中获取
|
||||
const [items, setItems] = useState<Variable[]>([])
|
||||
useEffect(() => {
|
||||
getVariablesApi({ flow_id: flow.id }).then(
|
||||
// chat -》L1; diff -> 对比测试
|
||||
type === 'chat' ? getVariablesApi({ flow_id: flow.flow_id || flow.id }).then(
|
||||
res => setItems(res)
|
||||
) : getVariablesApi({ version_id: vid, flow_id: flow.flow_id || flow.id }).then(
|
||||
res => setItems(res)
|
||||
)
|
||||
}, [])
|
||||
@@ -56,9 +65,9 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
onStart(obj, str)
|
||||
}
|
||||
|
||||
return <div className="absolute right-20 bottom-32 w-[90%] max-w-[680px] flex flex-col gap-6 rounded-xl p-4 md:p-6 bg-[#1A1A1A]">
|
||||
return <div className="flex flex-col gap-6 rounded-xl p-4 ">
|
||||
<div className="max-h-[520px] overflow-y-auto">
|
||||
{items.map((item, i) => <div key={item.id} className="w-full text-sm" style={{color:"#fff"}}>
|
||||
{items.map((item, i) => <div key={item.id} className="w-full text-sm">
|
||||
{item.name}
|
||||
<span className="text-status-red">{item.required ? " *" : ""}</span>
|
||||
<div className="mt-2">
|
||||
@@ -68,11 +77,18 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
onChange={(val) => handleChange(i, val)}
|
||||
/> :
|
||||
item.type === VariableType.Select ?
|
||||
<Dropdown
|
||||
options={item.options.map(e => e.value)}
|
||||
onSelect={(val) => handleChange(i, val)}
|
||||
value={item.value}
|
||||
></Dropdown> :
|
||||
<Select onValueChange={(val) => handleChange(i, val)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{
|
||||
item.options.map(el => <SelectItem key={el.value} value={el.value}>{el.value}</SelectItem>)
|
||||
}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select> :
|
||||
item.type === VariableType.File ?
|
||||
<InputFileComponent
|
||||
isSSO
|
||||
@@ -90,6 +106,8 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button size="sm" className="shengcheng-btn" onClick={handleStart}>{t('report.start')}</Button>
|
||||
{type === 'chat' && <Button size="sm" className="shengcheng-btn" onClick={handleStart}>{t('report.start')}</Button>}
|
||||
</div>
|
||||
};
|
||||
});
|
||||
|
||||
export default ChatReportForm
|
||||
@@ -18,6 +18,10 @@ import robot3 from "../../assets/robot3.png";
|
||||
import duihuaItemTop from "../../assets/chat/duihua-item-top.png";
|
||||
import duihuaItemJia from "../../assets/chat/duihua-item-+.png";
|
||||
import duihuaItemGuan from "../../assets/chat/duihua-item-x.png";
|
||||
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";
|
||||
|
||||
|
||||
export default function SkillChatPage() {
|
||||
@@ -25,6 +29,9 @@ export default function SkillChatPage() {
|
||||
const [face, setFace] = useState(true);
|
||||
|
||||
const { t } = useTranslation()
|
||||
const [selectChat, setSelelctChat] = useState<any>({
|
||||
id: '', chatId: '', type: ''
|
||||
})
|
||||
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
@@ -39,45 +46,33 @@ export default function SkillChatPage() {
|
||||
);
|
||||
// 对话列表
|
||||
const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList()
|
||||
const chatIdRef = useRef('')
|
||||
|
||||
// select flow
|
||||
const handlerSelectFlow = async (node: FlowType) => {
|
||||
const handlerSelectFlow = async (card) => {
|
||||
// 会话ID
|
||||
chatIdRef.current = generateUUID(32)
|
||||
setOpen(false)
|
||||
const _chatId = generateUUID(32)
|
||||
// setOpen(false)
|
||||
// add list
|
||||
addChat({
|
||||
"flow_name": node.name,
|
||||
"flow_description": node.description,
|
||||
"flow_id": node.id,
|
||||
"chat_id": chatIdRef.current,
|
||||
"flow_name": card.name,
|
||||
"flow_description": card.desc,
|
||||
"flow_id": card.id,
|
||||
"chat_id": _chatId,
|
||||
"create_time": "-",
|
||||
"update_time": "-"
|
||||
"update_time": "-",
|
||||
"flow_type": card.flow_type
|
||||
})
|
||||
|
||||
const flow = await getFlowApi(node.id)
|
||||
setFlow(flow)
|
||||
setChatId(chatIdRef.current)
|
||||
setFace(false)
|
||||
setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type })
|
||||
setChatId(_chatId)
|
||||
}
|
||||
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
console.log('chat.id :>> ', chat);
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
chatIdRef.current = chat.chat_id
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setSelelctChat({ id: chat.flow_id, chatId: chat.chat_id, type: chat.flow_type })
|
||||
setChatId(chat.chat_id)
|
||||
setFace(false)
|
||||
}, 100, false)
|
||||
|
||||
|
||||
@@ -88,7 +83,7 @@ export default function SkillChatPage() {
|
||||
desc: t('chat.confirmDeleteChat'),
|
||||
onOk(next) {
|
||||
deleteChat(id);
|
||||
setFace(true)
|
||||
setSelelctChat({ id: '', chatId: '', type: '' })
|
||||
next()
|
||||
}
|
||||
})
|
||||
@@ -96,9 +91,16 @@ export default function SkillChatPage() {
|
||||
|
||||
|
||||
return <div className="flex">
|
||||
<div className="h-screen w-[288px] border-r xinDuiHua-box relative">
|
||||
<div className="h-screen w-[288px] relative">
|
||||
<div className="xinDuiHua absolute">
|
||||
<div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div>
|
||||
<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 className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div> */}
|
||||
{/* <div className="xinDuiHua-del cursor-pointer">
|
||||
<img src={duihuaDel} alt=""/>
|
||||
</div> */}
|
||||
@@ -110,9 +112,10 @@ export default function SkillChatPage() {
|
||||
className={` group item xinDuiHua-list-item relative hover:xinDuiHua-list-active cursor-pointer dark:hover:xinDuiHua-list-active ${chatId === chat.chat_id && 'xinDuiHua-list-active dark:xinDuiHua-list-active'}`}
|
||||
onClick={() => handleSelectChat(chat)}>
|
||||
<div>
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{/* {(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>} */}
|
||||
<TitleIconBg className="w-[40px] h-[40px]" img={chat.avatar_img} id={chat.avatar_color ? chat.avatar_color : chat.flow_id} ><img src={chat.avatar_img ? chat.avatar_img : (chat.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
{/* <img src={robot} alt=""/> */}
|
||||
<div>
|
||||
<p>{chat.flow_name}</p>
|
||||
@@ -132,13 +135,7 @@ export default function SkillChatPage() {
|
||||
</div>
|
||||
</div>
|
||||
{/* chat */}
|
||||
{face
|
||||
? <div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>
|
||||
: <div className="flex-1 chat-box h-screen relative">
|
||||
{flow && <ChatPanne chatId={chatId} flow={flow} />}
|
||||
</div>}
|
||||
<ChatPanne data={selectChat}></ChatPanne>
|
||||
{/* 选择对话技能 */}
|
||||
<SkillTemps
|
||||
flows={onlineFlows}
|
||||
|
||||
@@ -232,23 +232,23 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
// if (chat.isSend) return chat.files.length ? <>
|
||||
// 发送消息
|
||||
if (chat.isSend) return <div className="flex flex-col items-end">
|
||||
<div className="flex items-center avatarZk">
|
||||
<p className="mr-[11px] text-[13px]">{userName}</p>
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[20px] ml-[10px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <p className="mr-[11px] text-[13px]">{userName}</p> */}
|
||||
<img src={robot} className="w-[30px]" alt=""/>
|
||||
</div>
|
||||
<div className="mt-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[20px] ml-[10px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 文件 */ }
|
||||
// <div className="chat chat-end">
|
||||
@@ -286,31 +286,31 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
<img src={robot} className="w-[30px]" alt=""/>
|
||||
<p className="ml-[10px] text-[13px]">{userName}</p>
|
||||
{/* <p className="ml-[10px] text-[13px]">{userName}</p> */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btnM"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* {chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>} */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btnM"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
{chat.source !== SourceType.NONE && chat.end && sourceContent(chat.source)}
|
||||
</div>
|
||||
};
|
||||
|
||||
942
src/pages/ChatAppPage/mobile/ChatPanneM copy.tsx
Normal file
@@ -0,0 +1,942 @@
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { ClipboardList, FileInput, FileText, Send, StopCircle } from "lucide-react";
|
||||
import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShadTooltip from "../../../components/ShadTooltipComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { getChatHistory, postBuildInit, postValidatePrompt } from "../../../controllers/API";
|
||||
import { Variable } from "../../../controllers/API/flow";
|
||||
import { sendAllProps } from "../../../types/api";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { FlowType, NodeType } from "../../../types/flow";
|
||||
import { validateNode } from "../../../utils";
|
||||
import { ChatMessage } from "./ChatMessageM";
|
||||
import ChatReportForm from "../components/ChatReportForm";
|
||||
import ResouceModal from "../components/ResouceModal";
|
||||
import ThumbsMessage from "../components/ThumbsMessage";
|
||||
import NpcInfo from "./NpcInfoM";
|
||||
import DuiHua from "./DuiHuaM";
|
||||
import PopUp from "./popUp";
|
||||
import Quote from "./quote";
|
||||
import { locationContext } from "../../../contexts/locationContext";
|
||||
import titIconL from "../../../assets/chatM/tit-icon-l.png";
|
||||
import titIconR from "../../../assets/chatM/tit-icon-r.png";
|
||||
import duihuaSend from "../../../assets/chat/duihua-send.png";
|
||||
|
||||
interface Iprops {
|
||||
chatId: string
|
||||
flow: FlowType
|
||||
queryString?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export default forwardRef(function ChatPanne({ chatId, flow, queryString, version = 'v1' }: Iprops,ref) {
|
||||
const { t } = useTranslation()
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const { isRoom, isForm, isReport, checkPrompt } = useFlowState(flow)
|
||||
|
||||
// build
|
||||
const build = useBuild(flow, chatId)
|
||||
// 消息列表
|
||||
const { messages, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll } = useMessages(chatId, flow)
|
||||
// ws通信
|
||||
const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
|
||||
// 停止状态
|
||||
const [isStop, setIsStop] = useState(true)
|
||||
// 输入框状态
|
||||
const { inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight } = useInputState({ flow, chatId, chating, messages, isForm, isReport })
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
// npc信息
|
||||
const [isNpcInfo, setIsNpcInfo] = useState(false)
|
||||
// 对话信息
|
||||
const [isDuiHua, setIsDuiHua] = useState(false)
|
||||
// 弹窗信息
|
||||
const [isPopUp, setIsPopUp] = useState(false)
|
||||
// 引用弹窗信息
|
||||
const [isQuote, setIsQuote] = useState(false)
|
||||
// 开始构建&切换初始化会话
|
||||
const initChat = async () => {
|
||||
await checkPrompt(flow)
|
||||
await build()
|
||||
const historyData = version === 'v1' ? await loadHistory() : (initGuide(), [])
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
// 第一条消息,用来初始化会话
|
||||
sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
inputs: {},
|
||||
flow_id: flow.id,
|
||||
chat_id: chatId
|
||||
})
|
||||
|
||||
changeHistoryByScroll.current = false
|
||||
// 自动聚焦
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 500);
|
||||
|
||||
const isNewChat = historyData.length === 0 || historyData[0].id === 9999
|
||||
setFormShow(isNewChat && isForm)
|
||||
}
|
||||
useEffect(() => {
|
||||
initChat()
|
||||
}, [flow])
|
||||
// document.documentElement.addEventListener(
|
||||
// 'touchstart',
|
||||
// function (event) {
|
||||
// if (event.touches.length > 1) {
|
||||
// event.preventDefault();
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// passive: false,
|
||||
// },
|
||||
// );
|
||||
// sendmsg user name
|
||||
const sendUserName = useMemo(() => {
|
||||
const node = flow.data.nodes.find(el => el.data.type === 'AutoGenUser')
|
||||
return node?.data.node.template['name'].value || ''
|
||||
}, [flow])
|
||||
|
||||
const handleSend = async () => {
|
||||
const msg = inputRef.current?.value
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ''
|
||||
inputRef.current.style.height = 'auto'
|
||||
}
|
||||
setInputEmpty(true)
|
||||
}, 100);
|
||||
|
||||
if (msg.trim() === '') return
|
||||
|
||||
setInputState({ lock: true, errorMsg: '' });
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: msg },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
await checkReLinkWs(async () => {
|
||||
// await build()
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
})
|
||||
|
||||
const chatInfo = {
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
inputs: { ...input, [inputKey]: msg }
|
||||
}
|
||||
// @ts-ignore
|
||||
isRoom && chating ? sendAll({ action: "continue", ...chatInfo })
|
||||
: sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
...chatInfo
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 报表请求
|
||||
const sendReport = (items: Variable[], str) => {
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: str },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
const data = items.map(item => ({
|
||||
id: item.nodeId,
|
||||
name: item.name,
|
||||
file_path: item.type === 'file' ? item.value : '',
|
||||
value: item.type === 'file' ? '' : item.value
|
||||
}))
|
||||
|
||||
setIsStop(false)
|
||||
setFormShow(false)
|
||||
|
||||
sendAll({
|
||||
inputs: {
|
||||
...input,
|
||||
[inputKey]: str,
|
||||
data
|
||||
},
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 溯源
|
||||
const [souce, setSouce] = useState<ChatMessageType>(null)
|
||||
|
||||
const thumbRef = useRef(null)
|
||||
|
||||
return <div className="overflow-hidden relative duihua-chat">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<div className="absolute chatShareM-tit">
|
||||
<div></div>
|
||||
{/* <div className="cursor-pointer" onClick={() => setIsDuiHua(!isDuiHua)}>
|
||||
<img src={titIconL} alt=""/>
|
||||
</div> */}
|
||||
<p>{flow.name}</p>
|
||||
{/* <div className="cursor-pointer" onClick={() => setIsNpcInfo(!isNpcInfo)}>
|
||||
<img src={titIconR} alt=""/>
|
||||
</div> */}
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="chata mt-[50px]" style={{ height: 'calc(100% - 50px)' }}>
|
||||
{/* 会话记录 */}
|
||||
<div ref={messagesRef} className={`chat-panne h-full overflow-y-scroll no-scrollbar px-[15px] ${isRoom || isReport ? 'pb-40' : 'pb-[60px]'}`}>
|
||||
{
|
||||
messages.map((c, i) => <ChatMessage
|
||||
key={c.id || i}
|
||||
userName={sendUserName}
|
||||
chat={c}
|
||||
disabledReSend={inputDisabled}
|
||||
showSearch={!!appConfig.dialogQuickSearch}
|
||||
onSource={() => setSouce(c)}
|
||||
onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }}
|
||||
onReSend={(msg) => {
|
||||
inputRef.current.value = msg
|
||||
handleSend()
|
||||
}}
|
||||
onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }}
|
||||
onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))}
|
||||
></ChatMessage>)
|
||||
}
|
||||
</div>
|
||||
{/* 输入框 */}
|
||||
<div className="absolute w-full bottom-0 duihua-input-box pb-[10px]">
|
||||
{/* <div className={`relative duihua-input
|
||||
${inputDisabled && 'bg-gray-200 dark:bg-gray-600'}`}> */}
|
||||
<div className={`relative duihua-input`}>
|
||||
<textarea id='input'
|
||||
ref={inputRef}
|
||||
disabled={inputDisabled} rows={1}
|
||||
className={`w-full resize-none border-none bg-transparent outline-none max-h-[160px]`}
|
||||
placeholder={(inputDisabled ? "当前处于回复中或不支持输入状态" : t('chat.inputPlaceholder'))}
|
||||
onInput={handleTextAreaHeight}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) handleSend()
|
||||
}}></textarea>
|
||||
<div className="absolute right-0 bottom-0 w-[42px] duihua-input-btn cursor-pointer">
|
||||
{/* <ShadTooltip content={t('chat.sendTooltip')}>
|
||||
<button disabled={inputEmpty || inputDisabled} className=" disabled:text-gray-400" onClick={handleSend}><Send /></button>
|
||||
</ShadTooltip> */}
|
||||
<div className="duihua-input-btn-send">
|
||||
<img src={duihuaSend} onClick={handleSend} alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
{/* {inputState.errorMsg && <div className="bg-gray-200 absolute top-0 left-0 w-full h-full text-center text-gray-400 align-middle pt-4">{inputState.errorMsg}</div>} */}
|
||||
{inputState.errorMsg && <div className="absolute top-0 left-0 w-full h-full text-center align-middle pt-4" style={{background:"#2E1212",color:"#FF6060"}}>{inputState.errorMsg}</div>}
|
||||
</div>
|
||||
<p className="mb-2 text-center text-gray-400 text-sm">{appConfig.dialogTips}</p>
|
||||
</div>
|
||||
</div>
|
||||
{(isRoom || isReport) && <div className=" absolute w-full flex justify-center bottom-32 pointer-events-none">
|
||||
<Button className="rounded-full pointer-events-auto" variant="outline" disabled={isStop} onClick={() => { setIsStop(true); stop(); }}><StopCircle className="mr-2" />Stop</Button>
|
||||
</div>}
|
||||
{/* 源文件类型 */}
|
||||
<ResouceModal chatId={chatId} open={!!souce} data={souce} setOpen={() => setSouce(null)}></ResouceModal>
|
||||
{/* 表单 */}
|
||||
{isForm && formShow && <ChatReportForm flow={flow} onStart={sendReport} />}
|
||||
{/* 踩 反馈 */}
|
||||
<ThumbsMessage ref={thumbRef}></ThumbsMessage>
|
||||
|
||||
<NpcInfo isNpcInfo={isNpcInfo} setIsNpcInfo={setIsNpcInfo}></NpcInfo>
|
||||
|
||||
<DuiHua isDuiHua={isDuiHua} setIsDuiHua={setIsDuiHua}></DuiHua>
|
||||
|
||||
<PopUp isPopUp={isPopUp} setIsPopUp={setIsPopUp}></PopUp>
|
||||
|
||||
<Quote isQuote={isQuote} setIsQuote={setIsQuote}></Quote>
|
||||
|
||||
</div>
|
||||
});
|
||||
/**
|
||||
* 输入框状态
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useInputState = ({ flow, chatId, chating, messages, isForm, isReport }) => {
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const [inputState, setInputState] = useState({
|
||||
lock: false,
|
||||
errorMsg: ''
|
||||
})
|
||||
// 输入问答
|
||||
const inputRef = useRef(null)
|
||||
useEffect(() => {
|
||||
!chating && setTimeout(() => {
|
||||
// 对话结束自动聚焦
|
||||
inputRef.current?.focus()
|
||||
}, 1000);
|
||||
}, [chating])
|
||||
// input 滚动
|
||||
const [inputEmpty, setInputEmpty] = useState(true)
|
||||
useEffect(() => {
|
||||
setInputEmpty(true)
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
}, [chatId])
|
||||
|
||||
// 获取上传file input
|
||||
const fileInputs = useMemo(() => {
|
||||
return tabsState[flow.id]?.formKeysData?.input_keys?.filter((input: any) => input.type === 'file')
|
||||
}, [tabsState, flow])
|
||||
|
||||
const handleTextAreaHeight = (e) => {
|
||||
const textarea = e.target
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = textarea.scrollHeight + 'px'
|
||||
setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
// input disabled
|
||||
const inputDisabled = useMemo(() => {
|
||||
return inputState.lock
|
||||
// 表单 && 没回话或只有一个引导词
|
||||
|| (isForm && (messages.length === 0 || (messages.length === 1 && messages[0].id === 9999)))
|
||||
|| isReport
|
||||
}, [inputState, fileInputs, isReport])
|
||||
|
||||
// 表单收起
|
||||
const [formShow, setFormShow] = useState(true)
|
||||
return {
|
||||
inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flow state
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useFlowState = (flow: FlowType) => {
|
||||
const flowSate = useMemo(() => {
|
||||
// 是否群聊
|
||||
const isRoom = !!flow.data?.nodes.find(node => node.data.type === "AutoGenChain")
|
||||
// 是否展示表单
|
||||
const isForm = !!flow.data?.nodes.find(node => ["VariableNode", "InputFileNode"].includes(node.data.type))
|
||||
// 是否报表
|
||||
const isReport = !!flow.data?.nodes.find(node => "Report" === node.data.type)
|
||||
return { isRoom, isForm, isReport }
|
||||
}, [flow])
|
||||
|
||||
// propmt类型补充自定义字段
|
||||
const checkPrompt = async (_flow) => {
|
||||
const params = _flow.data.nodes.map(node => {
|
||||
const temps = []
|
||||
const temp = node.data.node.template
|
||||
Object.keys(temp).map(key => {
|
||||
const { type, value } = temp[key]
|
||||
if (type === 'prompt' && !!value) !temps.length && temps.push({ name: key, template: value, data: node.data })
|
||||
})
|
||||
return temps
|
||||
}).flat()
|
||||
|
||||
const promises = params.map(param => {
|
||||
return postValidatePrompt(param.name, param.template, param.data.node).then(res => {
|
||||
if (res) param.data.node = res.frontend_node
|
||||
})
|
||||
})
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
return { ...flowSate, checkPrompt }
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表模块
|
||||
* 翻页、追加、历史
|
||||
* @returns
|
||||
*/
|
||||
const useMessages = (chatId, flow) => {
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const lastIdRef = useRef(0)
|
||||
// 控制开启自动随消息滚动(临时方案)
|
||||
const changeHistoryByScroll = useRef(false)
|
||||
|
||||
const loadIdRef = useRef('') // 记录最后一个加载的 chatId
|
||||
// 获取聊天记录
|
||||
const loadHistory = async (lastId?: number) => {
|
||||
loadIdRef.current = chatId
|
||||
|
||||
const res = await getChatHistory(flow.id, chatId, lastId ? 10 : 30, lastId)
|
||||
const hisData = res.map(item => {
|
||||
// let count = 0
|
||||
let { message, files, is_bot, intermediate_steps, ...other } = item
|
||||
try {
|
||||
message = message && message[0] === '{' ? JSON.parse(message.replace(/([\t\n"])/g, '\\$1').replace(/'/g, '"')) : message || ''
|
||||
} catch (e) {
|
||||
// 未考虑的情况暂不处理
|
||||
}
|
||||
return {
|
||||
...other,
|
||||
chatKey: typeof message === 'string' ? undefined : Object.keys(message)[0],
|
||||
end: true,
|
||||
files: files ? JSON.parse(files) : [],
|
||||
isSend: !is_bot,
|
||||
message,
|
||||
thought: intermediate_steps,
|
||||
noAccess: true
|
||||
}
|
||||
})
|
||||
lastIdRef.current = hisData[hisData.length - 1]?.id || lastIdRef.current || 0 // 记录最后一个id
|
||||
|
||||
let historyData = []
|
||||
if (lastId) {
|
||||
historyData = [...hisData.reverse(), ...chatHistory]
|
||||
} else if (loadIdRef.current === chatId) { // 保证同一会话
|
||||
historyData = hisData.reverse()
|
||||
}
|
||||
setChatHistory(historyData)
|
||||
const pageSize = historyData.length < 30 ? 30 : 10 // 先偷懒
|
||||
if (hisData.length < pageSize) initGuide()
|
||||
return historyData
|
||||
}
|
||||
|
||||
const loadLock = useRef(false)
|
||||
const currentIdRef = useRef(0)
|
||||
const loadNextPage = async () => {
|
||||
if (loadLock.current) return
|
||||
if (currentIdRef.current === lastIdRef.current) return // 最后一个相同表示聊天记录已到顶
|
||||
loadLock.current = true
|
||||
currentIdRef.current = lastIdRef.current
|
||||
changeHistoryByScroll.current = true
|
||||
await loadHistory(currentIdRef.current)
|
||||
loadLock.current = false
|
||||
// 滚动 hack TODO 滚动翻页设计
|
||||
setTimeout(() => {
|
||||
changeHistoryByScroll.current = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const initGuide = () => {
|
||||
const guideMsg = {
|
||||
"category": "system",
|
||||
"chat_id": chatId,
|
||||
"end": true,
|
||||
"create_time": "",
|
||||
"extra": "{}",
|
||||
"files": [],
|
||||
"flow_id": flow.id,
|
||||
"id": 9999,
|
||||
"thought": flow.guide_word,
|
||||
"is_bot": true,
|
||||
"liked": 0,
|
||||
"message": '',
|
||||
"receiver": null,
|
||||
"remark": null,
|
||||
"sender": "",
|
||||
"solved": 0,
|
||||
isSend: false,
|
||||
"source": 0,
|
||||
"type": "end",
|
||||
"update_time": "",
|
||||
noAccess: true,
|
||||
"user_id": 0
|
||||
}
|
||||
flow.guide_word && setChatHistory((chatHistory) =>
|
||||
chatHistory[0]?.id === 9999 ? chatHistory : [guideMsg, ...chatHistory]
|
||||
)
|
||||
}
|
||||
|
||||
// 消息滚动
|
||||
const messagesRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (messagesRef.current && !changeHistoryByScroll.current) { // 滚动加载不触发
|
||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
||||
}
|
||||
}, [chatHistory, changeHistoryByScroll]);
|
||||
|
||||
// 消息滚动加载
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
if (messagesRef.current.scrollTop <= 30) {
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
messagesRef.current?.addEventListener('scroll', handleScroll);
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, chatHistory, chatId]);
|
||||
|
||||
return {
|
||||
messages: chatHistory, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 通信
|
||||
* 建立连接、重连、断开、接收、发送
|
||||
* @returns
|
||||
*/
|
||||
const useWebsocket = (chatId, flow, setChatHistory, queryString, version) => {
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
// 接收ws状态
|
||||
const [begin, setBegin] = useState(false)
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
const chatIdRef = useRef(chatId);
|
||||
useEffect(() => {
|
||||
chatIdRef.current = chatId;
|
||||
}, [chatId])
|
||||
|
||||
function heartbeat() {
|
||||
if (!ws.current) return;
|
||||
if (ws.current.readyState !== 1) return;
|
||||
ws.current.send("heartbeat");
|
||||
setTimeout(heartbeat, 30000);
|
||||
}
|
||||
|
||||
function getWebSocketUrl(flowId, isDevelopment = false) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
|
||||
const isSecureProtocol = window.location.protocol === "https:";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = appConfig.websocketHost || window.location.host // isDevelopment ? "localhost:7860" : window.location.host;
|
||||
const chatEndpoint = version === 'v1' ? `/api/v1/chat/${flowId}?type=L1&chat_id=${chatId}&t=${token}`
|
||||
: `/api/v2/chat/ws/${flowId}?type=L1&chat_id=${chatId}${queryString}&t=${token}`
|
||||
|
||||
return `${webSocketProtocol}://${host}${chatEndpoint}`;
|
||||
}
|
||||
|
||||
const newChatStart = useRef(false) // 处理当前会话上下文丢失,阻止上一次打字机效果
|
||||
// 自动重连次数
|
||||
const tryReLinkCount = useRef(0)
|
||||
const reConnect = (params) => {
|
||||
if (tryReLinkCount.current <= 3) {
|
||||
connectWS(params)
|
||||
tryReLinkCount.current++
|
||||
} else {
|
||||
console.warn('超过最大重试次数 :>> ');
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
tryReLinkCount.current = 0
|
||||
newChatStart.current = true
|
||||
}, [chatId])
|
||||
|
||||
function connectWS(params) {
|
||||
const { setInputState, setIsStop, changeHistoryByScroll } = params
|
||||
if (ws.current) return Promise.resolve('ok');
|
||||
|
||||
// 连接断开重链接
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const urlWs = getWebSocketUrl(
|
||||
flow.id,
|
||||
process.env.NODE_ENV === "development"
|
||||
);
|
||||
const newWs = new WebSocket(urlWs);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
res('ok')
|
||||
// heartbeat()
|
||||
};
|
||||
newWs.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.chat_id !== chatIdRef.current) return
|
||||
console.log('newChatStart.current :>> ', newChatStart.current);
|
||||
|
||||
const errorMsg = data.category === 'error' ? data.intermediate_steps : ''
|
||||
|
||||
if (newChatStart.current) {
|
||||
if (data.type === 'close') {
|
||||
newChatStart.current = false
|
||||
return setInputState({ lock: false, errorMsg })
|
||||
} else {
|
||||
return setInputState({ lock: true, errorMsg })
|
||||
}
|
||||
}
|
||||
// 异常类型处理,提示
|
||||
if (errorMsg) return setInputState({ lock: true, errorMsg })
|
||||
|
||||
handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll });
|
||||
// get chat history
|
||||
// 群聊@自己时,开启input
|
||||
if (data.type === 'end' && data.receiver?.is_self) {
|
||||
setInputState({ lock: false, errorMsg: '' })
|
||||
}
|
||||
};
|
||||
newWs.onclose = (event) => {
|
||||
ws.current = null
|
||||
|
||||
handleOnClose({ event, setIsStop, setInputState });
|
||||
// reConnect(params)
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
ws.current = null
|
||||
|
||||
console.error('链接异常error', ev);
|
||||
setIsStop(true)
|
||||
|
||||
setErrorData({
|
||||
title: `${t('chat.networkError')}:`,
|
||||
list: [
|
||||
t('chat.networkErrorList1'),
|
||||
t('chat.networkErrorList2'),
|
||||
t('chat.networkErrorList3')
|
||||
],
|
||||
});
|
||||
reConnect(params)
|
||||
};
|
||||
ws.current = newWs;
|
||||
console.log('newWs :>> ', newWs);
|
||||
} catch (error) {
|
||||
console.error('创建链接异常', error);
|
||||
rej(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var isStream = false;
|
||||
function handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll }) {
|
||||
if (Array.isArray(data) && data.length) return
|
||||
if (data.type === "begin") {
|
||||
setBegin(true)
|
||||
setIsStop(false)
|
||||
changeHistoryByScroll.current = false
|
||||
}
|
||||
if (data.type === "close") {
|
||||
setBegin(false)
|
||||
setIsStop(true)
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
changeHistoryByScroll.current = true
|
||||
}
|
||||
if (data.type === "start") {
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: false,
|
||||
message: '',
|
||||
chatKey: '',
|
||||
thought: data.intermediate_steps || '',
|
||||
category: data.category || '',
|
||||
files: [],
|
||||
end: false
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
isStream = true;
|
||||
}
|
||||
if (data.type === "stream" && isStream) {
|
||||
updateLastMessage({ str: data.message, thought: data.intermediate_steps });
|
||||
}
|
||||
if (data.type === "end") {
|
||||
updateLastMessage({
|
||||
...data,
|
||||
str: data.message,
|
||||
files: data.files || null,
|
||||
end: true,
|
||||
thought: data.intermediate_steps || '',
|
||||
cate: data.category || '',
|
||||
messageId: data.message_id,
|
||||
noAccess: false,
|
||||
liked: 0
|
||||
});
|
||||
|
||||
isStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastMessage({ str, thought = '', end = false, files = [], cate = '', messageId = 0, source = false, noAccess = false, ...data }: {
|
||||
str: string;
|
||||
messageId?: number
|
||||
thought?: string;
|
||||
cate?: string;
|
||||
end?: boolean;
|
||||
files?: Array<any>;
|
||||
source?: boolean
|
||||
noAccess?: boolean
|
||||
}) {
|
||||
setChatHistory((old) => {
|
||||
const newChats = [...old]
|
||||
// console.log('newchats :>> ', newChats);
|
||||
let chatsLen = newChats.length
|
||||
const prevChat = newChats[chatsLen - 2]
|
||||
// hack 过滤重复最后消息
|
||||
if (end
|
||||
&& str
|
||||
&& chatsLen > 1
|
||||
&& str === prevChat.message
|
||||
// && data.sender === prevChat.sender
|
||||
&& !prevChat.thought) {
|
||||
newChats.splice(chatsLen - 2, 1) // 删上一条
|
||||
chatsLen = newChats.length
|
||||
}
|
||||
// 更新
|
||||
const lastChat = newChats[chatsLen - 1]
|
||||
const newLastChat = {
|
||||
...newChats[chatsLen - 1],
|
||||
...data,
|
||||
id: messageId,
|
||||
message: lastChat.message + str,
|
||||
thought: lastChat.thought + (thought ? `${thought}\n` : ''),
|
||||
files,
|
||||
category: cate,
|
||||
source,
|
||||
noAccess,
|
||||
end
|
||||
}
|
||||
newChats[chatsLen - 1] = newLastChat
|
||||
// start - end 之间没有内容删除load
|
||||
if (end && !(newLastChat.files.length || newLastChat.thought || newLastChat.message)) {
|
||||
newChats.pop()
|
||||
}
|
||||
return newChats;
|
||||
});
|
||||
}
|
||||
|
||||
// 发送ws
|
||||
async function sendAll(data: sendAllProps) {
|
||||
try {
|
||||
if (ws.current) {
|
||||
if (JSON.stringify(data.inputs) !== '{}') {
|
||||
newChatStart.current = false
|
||||
}
|
||||
ws.current.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主动断开
|
||||
function handleOnClose({ event, setIsStop, setInputState }) {
|
||||
console.error('链接手动断开 event :>> ', event);
|
||||
setIsStop(true)
|
||||
setBegin(false)
|
||||
|
||||
if ([1005, 1008].includes(event.code)) {
|
||||
console.warn('即将废弃 :>> ');
|
||||
setInputState({ lock: true, errorMsg: event.reason });
|
||||
} else {
|
||||
if (event.reason) {
|
||||
setErrorData({ title: event.reason });
|
||||
// setChatHistory((old) => {
|
||||
// let newChat = cloneDeep(old);
|
||||
// if (newChat.length) {
|
||||
// newChat[newChat.length - 1].end = true;
|
||||
// }
|
||||
// newChat.push({ end: true, message: `${t('chat.connectionbreakTip')}${event.reason}`, isSend: false, chatKey: '', files: [] });
|
||||
// return newChat
|
||||
// })
|
||||
}
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// destory
|
||||
return () => {
|
||||
// close prev connection
|
||||
if (ws.current) {
|
||||
switch (ws.current.readyState) {
|
||||
case WebSocket.OPEN:
|
||||
console.warn('前端主动关闭1')
|
||||
ws.current.close()
|
||||
; break;
|
||||
case WebSocket.CONNECTING:
|
||||
ws.current.onopen = () => {
|
||||
console.warn('前端主动关闭2')
|
||||
ws.current.close()
|
||||
};
|
||||
}
|
||||
ws.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 检测并重连
|
||||
const checkReLinkWs = async (reConnect) => {
|
||||
if (ws.current) return true
|
||||
// 重连
|
||||
// 上一条加loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = 'loading';
|
||||
return newChat;
|
||||
});
|
||||
await reConnect()
|
||||
// 链接成功
|
||||
// 上一条去loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = '';
|
||||
return newChat;
|
||||
});
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.current.send(JSON.stringify({
|
||||
"action": "stop"
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error stop the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { begin, stop: handleStop, checkReLinkWs, sendAll, connectWS }
|
||||
}
|
||||
|
||||
/**
|
||||
* build flow
|
||||
* 校验每个节点,展示进度及结果;返回input_keys;end_of_stream断开链接
|
||||
* 主要校验节点并设置更新setTabsState的 formKeysData
|
||||
* @returns
|
||||
*/
|
||||
const useBuild = (flow: FlowType, chatId: string) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { setTabsState } = useContext(TabsContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
// SSE 服务端推送
|
||||
async function streamNodeData(flow: FlowType, chatId: string) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const { flowId } = await postBuildInit(flow, chatId);
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
let buildEnd = false
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}?chat_id=${chatId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
eventSource.close(); // 结束关闭链接
|
||||
buildEnd = true
|
||||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
// setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys) {
|
||||
setTabsState((old) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// setProgress(parsedData.progress);
|
||||
validationResults.push(parsedData.valid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
finished = buildEnd // validationResults.length === flow.data.nodes.length;
|
||||
}
|
||||
// Step 4: Return true if all nodes are valid, false otherwise
|
||||
return validationResults.every((result) => result);
|
||||
}
|
||||
|
||||
// 延时器
|
||||
async function enforceMinimumLoadingTime(
|
||||
startTime: number,
|
||||
minimumLoadingTime: number
|
||||
) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = minimumLoadingTime - elapsedTime;
|
||||
|
||||
if (remainingTime > 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuild() {
|
||||
try {
|
||||
const errors = flow.data.nodes.flatMap((n: NodeType) => validateNode(n, flow.data.edges))
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: t('chat.buildError'),
|
||||
list: errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const minimumLoadingTime = 200; // in milliseconds
|
||||
const startTime = Date.now();
|
||||
|
||||
await streamNodeData(flow, chatId);
|
||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime); // 至少等200ms, 再继续(强制最小load时间)
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
return handleBuild
|
||||
}
|
||||
153
src/pages/ChatAppPage/mobile/chatShareM copy.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
// 嵌iframe、适配移动端
|
||||
import { useEffect, useMemo, useState, useRef, useContext } from "react";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import { getFlowApi, readOnlineFlows } from "../../../controllers/API/flow";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { generateUUID } from "../../../utils";
|
||||
import ChatPanneM from "./ChatPanneM";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteChatApi, getChatsApi } from "../../../controllers/API";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../../controllers/request";
|
||||
import { useDebounce, useTable } from "../../../util/hook";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import SkillTemps from "../../SkillPage/components/SkillTemps";
|
||||
import titIconL from "../../../assets/chatM/tit-icon-l.png";
|
||||
import titIconR from "../../../assets/chatM/tit-icon-r.png";
|
||||
|
||||
export default function chatShare() {
|
||||
const { id: flowId } = useParams()
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const libId = searchParams.get('lib')
|
||||
const tweak = searchParams.get('tweak')
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false)
|
||||
// 对话列表
|
||||
const { chatList, chatsRef, addChat, deleteChat } = useChatList()
|
||||
const queryString = useMemo(() => {
|
||||
const params = [];
|
||||
|
||||
if (libId) params.push(`knowledge_id=${libId}`);
|
||||
if (tweak) params.push(`tweak=${tweak}`);
|
||||
|
||||
return params.length > 0 ? `&${params.join('&')}` : '';
|
||||
}, [libId, tweak])
|
||||
const chatIdRef = useRef('')
|
||||
const handlerSelectFlow = async (node: FlowType) => {
|
||||
// 会话ID
|
||||
chatIdRef.current = generateUUID(32)
|
||||
setOpen(false)
|
||||
// add list
|
||||
addChat({
|
||||
"flow_name": node.name,
|
||||
"flow_description": node.description,
|
||||
"flow_id": node.id,
|
||||
"chat_id": chatIdRef.current,
|
||||
"create_time": "-",
|
||||
"update_time": "-"
|
||||
})
|
||||
|
||||
const flow = await getFlowApi(node.id)
|
||||
setFlow(flow)
|
||||
setChatId(chatIdRef.current)
|
||||
// setFace(false)
|
||||
}
|
||||
//
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const {
|
||||
data: onlineFlows,
|
||||
loading,
|
||||
search,
|
||||
} = useTable<FlowType>({}, (param) =>
|
||||
readOnlineFlows(param.page, param.keyword).then((res) => {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
const [chatId, setChatId] = useState<string>('')
|
||||
console.log(flowId,libId,tweak)
|
||||
useEffect(() => {
|
||||
flowId && getFlowApi(flowId).then(node => {
|
||||
// 会话ID
|
||||
setFlow(node)
|
||||
setChatId(generateUUID(32))
|
||||
})
|
||||
}, [flowId])
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
|
||||
const wsUrl = useMemo(() => {
|
||||
const params = [];
|
||||
|
||||
if (libId) params.push(`knowledge_id=${libId}`);
|
||||
if (tweak) params.push(`tweak=${tweak}`);
|
||||
|
||||
const paramStr = params.length > 0 ? `${params.join('&')}` : '';
|
||||
|
||||
return `/api/v2/chat/ws/${flowId}?type=L1&${paramStr}`
|
||||
}, [libId, tweak])
|
||||
|
||||
const [data] = useState<any>({ id: flowId, chatId: generateUUID(32), type: 'flow' })
|
||||
|
||||
if (!flowId) return <div>请选择技能</div>
|
||||
|
||||
return <div className="chatShareM">
|
||||
{/* <div className="chatShareM-tit">
|
||||
<img src={titIconL} alt=""/>
|
||||
<p>对话名称</p>
|
||||
<img src={titIconR} alt=""/>
|
||||
</div> */}
|
||||
{/* {flow
|
||||
? <div className="flex-1 chat-box h-screen relative">
|
||||
{flow && <ChatPanneM version='v2' queryString={queryString} chatId={chatId} flow={flow} />}
|
||||
</div>
|
||||
:<div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>} */}
|
||||
<ChatPanneM customWsHost={wsUrl} data={data} />
|
||||
</div>
|
||||
// flow ? <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} /> : null
|
||||
};
|
||||
/**
|
||||
* 本地对话列表
|
||||
*/
|
||||
const useChatList = () => {
|
||||
const [chatList, setChatList] = useState([])
|
||||
const chatsRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
getChatsApi().then(setChatList)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
chatList,
|
||||
chatsRef,
|
||||
addChat: (chat) => {
|
||||
const newList = [chat, ...chatList]
|
||||
// localStorage.setItem(ITEM_KEY, JSON.stringify(newList))
|
||||
setChatList(newList)
|
||||
setTimeout(() => {
|
||||
chatsRef.current.scrollTop = 1
|
||||
}, 0);
|
||||
},
|
||||
deleteChat: (id: string) => {
|
||||
// api
|
||||
captureAndAlertRequestErrorHoc(deleteChatApi(id).then(res => {
|
||||
setChatList(oldList => oldList.filter(item => item.chat_id !== id))
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,21 +73,35 @@ export default function chatShare() {
|
||||
setChatId(generateUUID(32))
|
||||
})
|
||||
}, [flowId])
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
|
||||
const wsUrl = useMemo(() => {
|
||||
const params = [];
|
||||
|
||||
if (libId) params.push(`knowledge_id=${libId}`);
|
||||
if (tweak) params.push(`tweak=${tweak}`);
|
||||
|
||||
const paramStr = params.length > 0 ? `${params.join('&')}` : '';
|
||||
|
||||
return `/api/v2/chat/ws/${flowId}?type=L1&${paramStr}`
|
||||
}, [libId, tweak])
|
||||
|
||||
const [data] = useState<any>({ id: flowId, chatId: generateUUID(32), type: 'flow' })
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
if (!flowId) return <div>请选择技能</div>
|
||||
|
||||
return <div className="chatShareM">
|
||||
@@ -96,13 +110,14 @@ export default function chatShare() {
|
||||
<p>对话名称</p>
|
||||
<img src={titIconR} alt=""/>
|
||||
</div> */}
|
||||
{flow
|
||||
{/* {flow
|
||||
? <div className="flex-1 chat-box h-screen relative">
|
||||
{flow && <ChatPanneM version='v2' queryString={queryString} chatId={chatId} flow={flow} />}
|
||||
</div>
|
||||
:<div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>}
|
||||
</div>} */}
|
||||
<ChatPanneM customWsHost={wsUrl} data={data} />
|
||||
</div>
|
||||
// flow ? <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} /> : null
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function FilesPage() {
|
||||
})
|
||||
)
|
||||
// loadData();
|
||||
setTimeout(() => reload(), 5000);
|
||||
// setTimeout(() => reload(), 5000);
|
||||
|
||||
const [hasPermission, setHasPermission] = useState(true)
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
@@ -13,7 +13,7 @@ const SelectComp = ({ value, onChange = (id) => { }, data, disabled = false }) =
|
||||
}
|
||||
|
||||
return <Select value={value} onValueChange={handleChange} disabled={disabled}>
|
||||
<SelectTrigger className="w-[120px] h-6">
|
||||
<SelectTrigger className="w-[61px] h-[27px] SelectTrigger">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -41,7 +41,7 @@ export default function CardSelectVersion(
|
||||
<TooltipTrigger>
|
||||
<SelectComp {...props} value={value} onChange={setValue} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<TooltipContent className="bg-[#E6B71E] text-[#000]">
|
||||
<p>{t('skills.chooseOnline')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LoadIcon } from "../../../components/bs-icons/loading";
|
||||
import { Button } from "../../../components/bs-ui/button";
|
||||
@@ -7,6 +7,10 @@ import { Input, Textarea } from "../../../components/bs-ui/input";
|
||||
import { createAssistantsApi } from "../../../controllers/API/assistant";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../../controllers/request";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import { uploadFileWithProgress, uploadNpcHeaderLibFileWithProgress } from "../../../modals/UploadModal/upload";
|
||||
import huifumoren from "../../../assets/npc/huifumoren.png";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
|
||||
export default function CreateAssistant() {
|
||||
|
||||
@@ -67,7 +71,13 @@ export default function CreateAssistant() {
|
||||
setErrors(formErrors);
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const [avatar_img, setAvatar_img] = useState("")
|
||||
const [avatar_color, setAvatar_color] = useState("")
|
||||
const randomNum = Math.floor(Math.random()*(4-0+1)+0).toString();
|
||||
useEffect(() => {
|
||||
if (avatar_color != "") return
|
||||
setAvatar_color(randomNum);
|
||||
}, [avatar_color]);
|
||||
// Handle form submission
|
||||
const navigate = useNavigate()
|
||||
const handleSubmit = async (e) => {
|
||||
@@ -77,7 +87,7 @@ export default function CreateAssistant() {
|
||||
if (isValid) {
|
||||
console.log('Form data:', formData);
|
||||
setLoading(true)
|
||||
const res = await captureAndAlertRequestErrorHoc(createAssistantsApi(formData.name, formData.roleAndTasks))
|
||||
const res = await captureAndAlertRequestErrorHoc(createAssistantsApi(formData.name, formData.roleAndTasks, avatar_img, avatar_color))
|
||||
if (res) {
|
||||
window.assistantCreate = true // 标记新建助手
|
||||
navigate('/assistant/' + res.id)
|
||||
@@ -85,12 +95,46 @@ export default function CreateAssistant() {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
const handleButtonClick = () => {
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
|
||||
input.onchange = (e: Event) => {
|
||||
setLoading(true);
|
||||
// Get the selected file
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
// Check if the file type is correct
|
||||
// Upload the file
|
||||
uploadNpcHeaderLibFileWithProgress(file, (progress) => { }).then(res => {
|
||||
setLoading(false);
|
||||
setAvatar_img(res);
|
||||
})
|
||||
};
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
};
|
||||
|
||||
return <DialogContent className="sm:max-w-[625px] bg-[#000000]">
|
||||
return <DialogContent className="sm:max-w-[625px] bg-[#262626]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[#fff]">创建NPC</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-8 py-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="bisheng-label text-[#999999]"><span className="bisheng-tip text-[#FF6060]">* </span>NPC头像</label>
|
||||
{/* <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 : npcIcon} alt="" /></TitleIconBg> */}
|
||||
|
||||
<div className="flex items-center ml-[7px] mt-[6px]">
|
||||
<TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" img={avatar_img} id={avatar_color} ><img onClick={handleButtonClick} src={avatar_img ? avatar_img : npcIcon} alt="" /></TitleIconBg>
|
||||
<div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setAvatar_img("")}>
|
||||
<img src={huifumoren} className="w-[12px] h-[11px]" alt="" />
|
||||
<span className="ml-[5px] text-[#999999] text-[12px] mt-[1px]">恢复默认</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<label htmlFor="name" className="bisheng-label text-[#999999]"><span className="bisheng-tip text-[#FF6060]">* </span>NPC名称</label>
|
||||
<Input id="name" name="name" placeholder="给NPC取一个名字" className="mt-2 npcInput" value={formData.name} onChange={handleChange} />
|
||||
|
||||
@@ -14,6 +14,7 @@ import { captureAndAlertRequestErrorHoc } from "@/controllers/request"
|
||||
import { PlusIcon } from "@radix-ui/react-icons"
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
|
||||
const TestDialog = forwardRef((props: any, ref) => {
|
||||
const {t} = useTranslation()
|
||||
@@ -116,7 +117,7 @@ const TestDialog = forwardRef((props: any, ref) => {
|
||||
<Button className="bg-[#ffd025] hover:bg-[#ffd025] text-[#333]" onClick={handleTest} disabled={loading}>{t('test.test')}</Button>
|
||||
<div className="">
|
||||
<label htmlFor="desc" className="bisheng-label text-[#fff]">{t('test.result')}</label>
|
||||
<Textarea id="desc" name="desc" value={result} placeholder={t('test.outResultPlaceholder')} readOnly className="mt-2 npcInput2 text-[#fff]" />
|
||||
<Textarea id="desc" name="desc" value={result} placeholder={t('test.outResultPlaceholder')} readOnly className="mt-2 npcInput2 text-[#fff] overflow-y-auto no-scrollbar" />
|
||||
</div>
|
||||
</div>}
|
||||
</DialogContent>
|
||||
@@ -159,6 +160,7 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
setShow(bln)
|
||||
}
|
||||
const [delShow, setDelShow] = useState(false)
|
||||
const [delShow1, setDelShow1] = useState(false)
|
||||
|
||||
const schemaUrl = useRef('')
|
||||
const [formState, setFormState] = useState({ ...formData });
|
||||
@@ -281,18 +283,24 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
|
||||
// 删除工具
|
||||
const handleDelete = () => {
|
||||
bsConfirm({
|
||||
title: t('prompt'),
|
||||
desc: t('skills.deleteSure'),
|
||||
onOk(next) {
|
||||
// api
|
||||
captureAndAlertRequestErrorHoc(deleteTool(fromDataRef.current.id)).then(res => {
|
||||
if (res === false) return
|
||||
props.onReload()
|
||||
setEditShow(false)
|
||||
next()
|
||||
})
|
||||
}
|
||||
// bsConfirm({
|
||||
// title: t('prompt'),
|
||||
// desc: t('skills.deleteSure'),
|
||||
// onOk(next) {
|
||||
// // api
|
||||
// captureAndAlertRequestErrorHoc(deleteTool(fromDataRef.current.id)).then(res => {
|
||||
// if (res === false) return
|
||||
// props.onReload()
|
||||
// setEditShow(false)
|
||||
// next()
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
captureAndAlertRequestErrorHoc(deleteTool(fromDataRef.current.id)).then(res => {
|
||||
if (res === false) return
|
||||
props.onReload()
|
||||
setEditShow(false)
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -305,93 +313,205 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
|
||||
return <div>
|
||||
<Sheet open={editShow} onOpenChange={setEditShow}>
|
||||
<SheetContent className="w-[800px] bg-[#000] sm:max-w-[800px] p-4">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{delShow ? t('edit') : t('create')}自定义工具</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="mt-4 overflow-y-auto h-screen pb-40">
|
||||
{/* name */}
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">名称</label>
|
||||
<div className="px-6 mb-4" >
|
||||
<Input
|
||||
id="toolName"
|
||||
name="toolName"
|
||||
className="mt-2 npcInput2"
|
||||
placeholder="输入工具名称"
|
||||
value={formState.toolName}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{/* schema */}
|
||||
<div className="px-6 flex items-center justify-between">
|
||||
<label htmlFor="open" className="text-[#fff]">OpenAPI Schema</label>
|
||||
<div className="flex gap-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="text-[#fff] bg-[#1a1a1a]"><PlusIcon /> 从URL导入</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="end">
|
||||
<div className="flex items-center gap-4 ">
|
||||
<Input
|
||||
id="schemaUrl"
|
||||
name="schemaUrl"
|
||||
placeholder="https://"
|
||||
onChange={(e) => schemaUrl.current = e.target.value}
|
||||
/>
|
||||
<PopoverClose>
|
||||
<Button size="sm" className="w-16 text-[#fff] bg-[#1a1a1a]" onClick={handleImportSchema}>{t('skills.import')}</Button>
|
||||
</PopoverClose>
|
||||
|
||||
<SheetContent className="flex w-[800px] bg-[#1a1a1a] sm:max-w-[800px]">
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="w-[800px] pt-[10px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{delShow ? t('edit') : t('create')}自定义工具</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="mt-4 overflow-y-auto h-screen pb-40">
|
||||
{/* name */}
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">名称</label>
|
||||
<div className="px-6 mb-4" >
|
||||
<Input
|
||||
id="toolName"
|
||||
name="toolName"
|
||||
className="mt-2 npcInput2"
|
||||
placeholder="输入工具名称"
|
||||
value={formState.toolName}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{/* schema */}
|
||||
<div className="px-6 flex items-center justify-between">
|
||||
<label htmlFor="open" className="text-[#fff]">OpenAPI Schema</label>
|
||||
<div className="flex gap-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="text-[#fff] bg-[#1a1a1a]"><PlusIcon /> 从URL导入</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="end">
|
||||
<div className="flex items-center gap-4 ">
|
||||
<Input
|
||||
id="schemaUrl"
|
||||
name="schemaUrl"
|
||||
placeholder="https://"
|
||||
onChange={(e) => schemaUrl.current = e.target.value}
|
||||
/>
|
||||
<PopoverClose>
|
||||
<Button size="sm" className="w-16 text-[#fff] bg-[#1a1a1a]" onClick={handleImportSchema}>{t('skills.import')}</Button>
|
||||
</PopoverClose>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Select value="1" onValueChange={(k) => handleSelectTemplate(k)}>
|
||||
<SelectTrigger >
|
||||
<span>{t('tools1.examples')}</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="json">{t('tools1.weatherJson')}</SelectItem>
|
||||
<SelectItem value="yaml">{t('tools1.petShopYaml')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 mb-4" >
|
||||
<Textarea
|
||||
id="schemaContent"
|
||||
name="schemaContent"
|
||||
placeholder={t('tools1.enterOpenAPISchema')}
|
||||
className="mt-2 min-h-52 npcInput"
|
||||
value={formState.schemaContent}
|
||||
onChange={handleInputChange}
|
||||
onBlur={() => handleSelectTemplate()}
|
||||
/>
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">{t('tools1.authenticationType')}</label>
|
||||
<div className="px-6">
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label text-[#999]">{t('tools1.authType')}</label>
|
||||
<RadioGroup
|
||||
id="authMethod"
|
||||
name="authMethod"
|
||||
defaultValue={formState.authMethod}
|
||||
className="flex mt-2 gap-4"
|
||||
onValueChange={(value) => setFormState(prevState => ({ ...prevState, authMethod: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="none" id="r1" />
|
||||
<Label htmlFor="r1" className="text-[#fff]">{t('tools1.none')}</Label>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Select value="1" onValueChange={(k) => handleSelectTemplate(k)}>
|
||||
<SelectTrigger >
|
||||
<span>{t('tools1.examples')}</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="json">{t('tools1.weatherJson')}</SelectItem>
|
||||
<SelectItem value="yaml">{t('tools1.petShopYaml')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="apikey" id="r2" />
|
||||
<Label htmlFor="r2" className="text-[#fff]">{t('tools1.apiKey')}</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{formState.authMethod === "apikey" && (<>
|
||||
<div className="px-6 mb-4">
|
||||
<label className="bisheng-label text-[#fff]" htmlFor="apiKey">API Key</label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
className="mt-2"
|
||||
value={formState.apiKey}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label text-[#fff]">Auth Type</label>
|
||||
<RadioGroup
|
||||
id="authType"
|
||||
name="authType"
|
||||
defaultValue={formState.authType}
|
||||
className="flex mt-2 gap-4"
|
||||
onValueChange={(value) => setFormState(prevState => ({ ...prevState, authType: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="basic" id="r4" />
|
||||
<Label htmlFor="r4">Basic</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="bearer" id="r5" />
|
||||
<Label htmlFor="r5">Bearer</Label>
|
||||
</div>
|
||||
{/* <div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="custom" id="r6" />
|
||||
<Label htmlFor="r6">Custom</Label>
|
||||
</div> */}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</>)}
|
||||
{/* {formState.authMethod === "custom" && (
|
||||
<div className="px-6 mb-4">
|
||||
<label htmlFor="customHeader">Custom Header Name</label>
|
||||
<Input
|
||||
id="customHeader"
|
||||
name="customHeader"
|
||||
className="mt-2"
|
||||
value={formState.customHeader}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">{t('tools1.availableTools')}</label>
|
||||
<div className="px-6 mb-4" >
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px] text-[#fff]">{t('tools1.name')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.description')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.method')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.path')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('operations')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{
|
||||
tableData.length ? tableData.map((item, index) =>
|
||||
<TableRow key={index}>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.desc}</TableCell>
|
||||
<TableCell>{item.extra.method}</TableCell>
|
||||
<TableCell>{item.extra.path}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => testDialogRef.current.open(item, fromDataRef.current)}
|
||||
>{t('test.test')}</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) :
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>{t('tools1.none')}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 mb-4" >
|
||||
<Textarea
|
||||
id="schemaContent"
|
||||
name="schemaContent"
|
||||
placeholder={t('tools1.enterOpenAPISchema')}
|
||||
className="mt-2 min-h-52 npcInput"
|
||||
value={formState.schemaContent}
|
||||
onChange={handleInputChange}
|
||||
onBlur={() => handleSelectTemplate()}
|
||||
/>
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">{t('tools1.authenticationType')}</label>
|
||||
<label htmlFor="open" className="px-6">{t('tools1.authenticationType')}</label>
|
||||
<div className="px-6">
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label text-[#999]">{t('tools1.authType')}</label>
|
||||
<label htmlFor="open" className="bisheng-label">{t('tools1.authType')}</label>
|
||||
<RadioGroup
|
||||
id="authMethod"
|
||||
name="authMethod"
|
||||
defaultValue={formState.authMethod}
|
||||
defaultValue="none"
|
||||
className="flex mt-2 gap-4"
|
||||
onValueChange={(value) => setFormState(prevState => ({ ...prevState, authMethod: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="none" id="r1" />
|
||||
<Label htmlFor="r1" className="text-[#fff]">{t('tools1.none')}</Label>
|
||||
<Label htmlFor="r1">{t('tools1.none')}</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="apikey" id="r2" />
|
||||
<Label htmlFor="r2" className="text-[#fff]">{t('tools1.apiKey')}</Label>
|
||||
<Label htmlFor="r2">{t('tools1.apiKey')}</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{formState.authMethod === "apikey" && (<>
|
||||
{formState.authMethod === "apikey" && (
|
||||
<div className="px-6 mb-4">
|
||||
<label className="bisheng-label text-[#fff]" htmlFor="apiKey">API Key</label>
|
||||
<label htmlFor="apiKey">{t('tools1.apiKey')}</label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
@@ -400,129 +520,33 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label text-[#fff]">Auth Type</label>
|
||||
<RadioGroup
|
||||
id="authType"
|
||||
name="authType"
|
||||
defaultValue={formState.authType}
|
||||
className="flex mt-2 gap-4"
|
||||
onValueChange={(value) => setFormState(prevState => ({ ...prevState, authType: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="basic" id="r4" />
|
||||
<Label htmlFor="r4">Basic</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="bearer" id="r5" />
|
||||
<Label htmlFor="r5">Bearer</Label>
|
||||
</div>
|
||||
{/* <div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="custom" id="r6" />
|
||||
<Label htmlFor="r6">Custom</Label>
|
||||
</div> */}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</>)}
|
||||
{/* {formState.authMethod === "custom" && (
|
||||
<div className="px-6 mb-4">
|
||||
<label htmlFor="customHeader">Custom Header Name</label>
|
||||
<Input
|
||||
id="customHeader"
|
||||
name="customHeader"
|
||||
className="mt-2"
|
||||
value={formState.customHeader}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">{t('tools1.availableTools')}</label>
|
||||
<div className="px-6 mb-4" >
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px] text-[#fff]">{t('tools1.name')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.description')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.method')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('tools1.path')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('operations')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{
|
||||
tableData.length ? tableData.map((item, index) =>
|
||||
<TableRow key={index}>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.desc}</TableCell>
|
||||
<TableCell>{item.extra.method}</TableCell>
|
||||
<TableCell>{item.extra.path}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => testDialogRef.current.open(item, fromDataRef.current)}
|
||||
>{t('test.test')}</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) :
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>{t('tools1.none')}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
<SheetFooter className="absolute bottom-0 right-0 w-full px-6 py-4">
|
||||
{delShow && <Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
className="absolute left-[50px]"
|
||||
onClick={() => setDelShow1(true)}
|
||||
>{t('tools1.delete')}</Button>}
|
||||
<Button size="sm" variant="outline" className="baogao-btn baogao-btn2" onClick={() => setEditShow(false)}>{t('tools1.cancel')}</Button>
|
||||
<Button size="sm" onClick={handleSave} className="baogao-btn baogao-btn2">{t('tools1.save')}</Button>
|
||||
</SheetFooter>
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6">{t('tools1.authenticationType')}</label>
|
||||
<div className="px-6">
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label">{t('tools1.authType')}</label>
|
||||
<RadioGroup
|
||||
id="authMethod"
|
||||
name="authMethod"
|
||||
defaultValue="none"
|
||||
className="flex mt-2 gap-4"
|
||||
onValueChange={(value) => setFormState(prevState => ({ ...prevState, authMethod: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="none" id="r1" />
|
||||
<Label htmlFor="r1">{t('tools1.none')}</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="apikey" id="r2" />
|
||||
<Label htmlFor="r2">{t('tools1.apiKey')}</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{formState.authMethod === "apikey" && (
|
||||
<div className="px-6 mb-4">
|
||||
<label htmlFor="apiKey">{t('tools1.apiKey')}</label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
className="mt-2"
|
||||
value={formState.apiKey}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SheetFooter className="absolute bottom-0 right-0 w-full px-6 py-4">
|
||||
{delShow && <Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
className="absolute left-6"
|
||||
onClick={handleDelete}
|
||||
>{t('tools1.delete')}</Button>}
|
||||
<Button size="sm" variant="outline" className="baogao-btn baogao-btn2" onClick={() => setEditShow(false)}>{t('tools1.cancel')}</Button>
|
||||
<Button size="sm" onClick={handleSave} className="baogao-btn baogao-btn2">{t('tools1.save')}</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet >
|
||||
{/* test dialog */}
|
||||
<TestDialog ref={testDialogRef} />
|
||||
<dialog className={`modal ${delShow1 && 'modal-open'}`}>
|
||||
<form method="dialog" className="modal-box w-[400px] bg-[#262626] shadow-lg">
|
||||
<h3 className="text-[16px] font-bold text-center" style={{color:"#FFFFFF"}}>{t('prompt')}</h3>
|
||||
<p className="text-[12px] text-center mt-[18px]" style={{color:"#FFFFFF"}}>确认删除该工具? 删除后数据无法恢复,请确认!</p>
|
||||
<div className="flex justify-center mt-[27px]">
|
||||
<Button className="baogao-btn" variant="outline" onClick={() => setDelShow1(false)}>{t('cancel')}</Button>
|
||||
<Button className="baogao-btn ml-[27px]" variant="destructive" onClick={handleDelete}>{t('delete')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
})
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ 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 { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
|
||||
export default function SkillTemps({ flows, isTemp = false,
|
||||
title = 'skills.skillTemplate',
|
||||
@@ -49,18 +51,20 @@ export default function SkillTemps({ flows, isTemp = false,
|
||||
<span>
|
||||
<div>
|
||||
{/* <img src={robot} className="w-[160px]" alt=""/> */}
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[160px]" alt=""/>}
|
||||
{/* {(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[160px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[160px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[160px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[160px]" alt=""/>} */}
|
||||
<TitleIconBg className="w-[160px] h-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : nengliIcon} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[42px]" alt=""/>}
|
||||
{/* {(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[42px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[42px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[42px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[42px]" alt=""/>} */}
|
||||
{/* <img src={robot} className="w-[42px]" alt=""/> */}
|
||||
<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 : nengliIcon} alt="" /></TitleIconBg>
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
<div>
|
||||
|
||||
@@ -178,7 +178,7 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
<Textarea ref={areaRef} className="h-full npcInput" defaultValue={assistantState.prompt}
|
||||
placeholder={t('build.prompt')}
|
||||
></Textarea>
|
||||
<Button className="group-hover:flex hidden h-6 absolute bottom-4 right-4" disabled={LoadType.Prompt <= loading} size="sm" onClick={handleUsePropmt}>{t('build.use')}</Button>
|
||||
<Button className="group-hover:flex hidden h-6 absolute bottom-4 right-4 baogao-btn2" disabled={LoadType.Prompt <= loading} size="sm" onClick={handleUsePropmt}>{t('build.use')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* 自动配置 */}
|
||||
@@ -190,7 +190,7 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
{/* 开场白 */}
|
||||
<div className="group relative pb-12 mt-4 px-4 py-2 rounded-md bg-[#1a1a1a]">
|
||||
<div className="text-md mb-2 font-medium leading-none flex text-[#fff]">{t('build.openingRemarks')}{LoadType.GuideWord === loading && <LoadIcon className="ml-2 text-gray-600" />}</div>
|
||||
<Textarea ref={guideAreaRef} className="bg-transparent border-none bg-gray-50 npcInput1"></Textarea>
|
||||
<Textarea ref={guideAreaRef} className="bg-transparent border-none bg-gray-50 npcInput1 overflow-y-auto no-scrollbar"></Textarea>
|
||||
<Button className="group-hover:flex hidden h-6 absolute bottom-4 right-4 baogao-btn2" disabled={LoadType.GuideWord <= loading} size="sm" onClick={handleUseGuide}>{t('build.use')}</Button>
|
||||
</div>
|
||||
{/* 引导词 */}
|
||||
@@ -198,7 +198,7 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
<div className="text-md mb-2 font-medium leading-none flex text-[#fff]">{t('build.guidingQuestions')}{LoadType.GuideQuestion === loading && <LoadIcon className="ml-2 text-gray-600" />}</div>
|
||||
{
|
||||
question.map(qs => (
|
||||
<p key={qs} className="text-sm text-[#666666] bg-[#262626] px-2 py-1 rounded-xl mb-2">{qs}</p>
|
||||
<p key={qs} className="text-sm text-[#999999] bg-[#262626] px-2 py-1 rounded-xl mb-2">{qs}</p>
|
||||
))
|
||||
}
|
||||
<Button className="group-hover:flex hidden h-6 absolute bottom-4 right-4 baogao-btn2" disabled={LoadType.GuideQuestion <= loading} size="sm" onClick={handleUserQuestion}>{t('build.use')}</Button>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
|
||||
import { Input, Textarea } from "@/components/bs-ui/input";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import npcIcon from "../../../../assets/npc/npcIcon.png";
|
||||
import huifumoren from "../../../../assets/npc/huifumoren.png";
|
||||
import { uploadNpcHeaderLibFileWithProgress } from "@/modals/UploadModal/upload";
|
||||
|
||||
export default function EditAssistantDialog({ name, desc, onSave }) {
|
||||
export default function EditAssistantDialog({ name, desc, avatar_img, avatar_color, onSave }) {
|
||||
|
||||
const { t } = useTranslation()
|
||||
// State for form fields
|
||||
const [formData, setFormData] = useState({ name: '', desc: '' });
|
||||
|
||||
const [formData, setFormData] = useState({ name: '', desc: '', avatar_img: '', avatar_color: '' });
|
||||
useEffect(() => {
|
||||
setFormData({ name, desc })
|
||||
}, [name, desc])
|
||||
// console.log(formData, name, desc);
|
||||
setFormData({ name, desc, avatar_img, avatar_color })
|
||||
}, [name, desc, avatar_img, avatar_color])
|
||||
console.log(formData, name, desc, avatar_img, avatar_color);
|
||||
|
||||
// State for errors
|
||||
const [errors, setErrors] = useState<any>({});
|
||||
@@ -76,27 +79,62 @@ export default function EditAssistantDialog({ name, desc, onSave }) {
|
||||
|
||||
};
|
||||
|
||||
return <DialogContent className="sm:max-w-[625px]">
|
||||
const handleButtonClick = () => {
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
|
||||
input.onchange = (e: Event) => {
|
||||
// setLoading(true);
|
||||
// Get the selected file
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
// Check if the file type is correct
|
||||
// Upload the file
|
||||
uploadNpcHeaderLibFileWithProgress(file, (progress) => { }).then(res => {
|
||||
// setLoading(false);
|
||||
setFormData({ name, desc, avatar_img: res, avatar_color })
|
||||
})
|
||||
};
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
};
|
||||
|
||||
return <DialogContent className="sm:max-w-[625px] bg-[#262626]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('build.editAssistant')}</DialogTitle>
|
||||
<DialogTitle className="text-[#fff]">{t('build.editAssistant')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-8 py-6">
|
||||
<div className="">
|
||||
<label htmlFor="name" className="bisheng-label">{t('build.assistantName')}<span className="bisheng-tip">*</span></label>
|
||||
<Input id="name" name="name" placeholder={t('build.enterName')} className="mt-2" value={formData.name} onChange={handleChange} />
|
||||
{errors.name && <p className="bisheng-tip mt-1">{errors.name}</p>}
|
||||
<div>
|
||||
<label htmlFor="name" className="bisheng-label text-[#999999]"><span className="bisheng-tip text-[#FF6060]">* </span>NPC头像</label>
|
||||
{/* <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 : npcIcon} alt="" /></TitleIconBg> */}
|
||||
|
||||
<div className="flex items-center ml-[7px] mt-[6px]">
|
||||
<TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" img={formData.avatar_img} id={formData.avatar_color} ><img onClick={handleButtonClick} src={formData.avatar_img ? formData.avatar_img : npcIcon} alt="" /></TitleIconBg>
|
||||
<div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setFormData({ name, desc, avatar_img: '', avatar_color })}>
|
||||
<img src={huifumoren} className="w-[12px] h-[11px]" alt="" />
|
||||
<span className="ml-[5px] text-[#999999] text-[12px] mt-[1px]">恢复默认</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<label htmlFor="desc" className="bisheng-label">{t('build.assistantDesc')}</label>
|
||||
<Textarea id="desc" name="desc" placeholder={t('build.enterDesc')} maxLength={1200} className="mt-2" value={formData.desc} onChange={handleChange} />
|
||||
<label htmlFor="name" className="bisheng-label text-[#999999]">{t('build.assistantName')}<span className="bisheng-tip text-[#FF6060]">*</span></label>
|
||||
<Input id="name" name="name" placeholder={t('build.enterName')} className="mt-2 npcInput" value={formData.name} onChange={handleChange} />
|
||||
{errors.name && <p className="bisheng-tip mt-1 text-[#999999]">{errors.name}</p>}
|
||||
</div>
|
||||
<div className="">
|
||||
<label htmlFor="desc" className="bisheng-label text-[#999999]">{t('build.assistantDesc')}</label>
|
||||
<Textarea id="desc" name="desc" placeholder={t('build.enterDesc')} maxLength={1200} className="mt-2 npcInput no-scrollbar" value={formData.desc} onChange={handleChange} />
|
||||
{errors.desc && <p className="bisheng-tip mt-1">{errors.desc}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button variant="outline" className="px-11" type="button">{t('build.cancel')}</Button>
|
||||
<Button variant="outline" className="px-11 baogao-btn baogao-btn2" type="button">{t('build.cancel')}</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="px-11" onClick={handleSubmit}>{t('build.confirm')}</Button>
|
||||
<Button type="submit" className="px-11 baogao-btn baogao-btn2" onClick={handleSubmit}>{t('build.confirm')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import EditAssistantDialog from "./EditAssistantDialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import npcIcon from "../../../../assets/npc/npcIcon.png";
|
||||
|
||||
export default function Header({ onSave, onLine }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -30,11 +31,11 @@ export default function Header({ onSave, onLine }) {
|
||||
setEditShow(false)
|
||||
needSaveRef.current = true
|
||||
}
|
||||
|
||||
return <div className="flex justify-between items-center border-b px-4">
|
||||
<div className="flex items-center gap-2 py-4">
|
||||
<Button variant="outline" size="icon" onClick={() => navigate(-1)}><ChevronLeftIcon className="h-4 w-4" /></Button>
|
||||
<TitleIconBg id={assistantState.id} className="ml-4"><AssistantIcon /></TitleIconBg>
|
||||
{/* <TitleIconBg id={assistantState.id} className="ml-4"><AssistantIcon /></TitleIconBg> */}
|
||||
<TitleIconBg className="w-[40px] h-[40px]" img={assistantState.avatar_img} id={assistantState.avatar_color ? assistantState.avatar_color : assistantState.id} ><img src={assistantState.avatar_img ? assistantState.avatar_img : npcIcon} alt="" /></TitleIconBg>
|
||||
<span className="bisheng-title text-[#fff]">{assistantState.name}</span>
|
||||
{/* edit dialog */}
|
||||
<Dialog open={editShow} onOpenChange={setEditShow}>
|
||||
@@ -45,6 +46,8 @@ export default function Header({ onSave, onLine }) {
|
||||
editShow && <EditAssistantDialog
|
||||
name={assistantState.name}
|
||||
desc={assistantState.desc}
|
||||
avatar_img={assistantState.avatar_img}
|
||||
avatar_color={assistantState.avatar_color ? assistantState.avatar_color : assistantState.id}
|
||||
onSave={handleEditSave}></EditAssistantDialog>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function Setting() {
|
||||
</label>
|
||||
<Textarea
|
||||
name="open"
|
||||
className="mt-2 min-h-[34px] npcInput2"
|
||||
className="mt-2 min-h-[34px] npcInput2 overflow-y-auto no-scrollbar"
|
||||
style={{ height: 56 }}
|
||||
placeholder={t("build.assistantMessageFormat")}
|
||||
value={assistantState.guide_word}
|
||||
@@ -180,7 +180,7 @@ export default function Setting() {
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<h1 className="border-b bg-[#1a1a1a] text-[#999] indent-4 text-sm leading-8">
|
||||
{t("build.abilities")}
|
||||
辅助
|
||||
</h1>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AssistantIcon } from "@/components/bs-icons/assistant";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import npcIcon from "../../../../assets/npc/npcIcon.png";
|
||||
|
||||
export default function TestChat({ assisId, guideQuestion }) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
@@ -40,7 +41,8 @@ export default function TestChat({ assisId, guideQuestion }) {
|
||||
|
||||
return <div className="relative h-full bs-chat-bg">
|
||||
<div className="absolute flex w-full left-0 top-0 gap-2 px-4 py-2 items-center z-10 bg-[#000] shadow-sm">
|
||||
<TitleIconBg className="" id={assistantState.id}><AssistantIcon /></TitleIconBg>
|
||||
{/* <TitleIconBg className="" id={assistantState.id}><AssistantIcon /></TitleIconBg> */}
|
||||
<TitleIconBg className="w-[24px] h-[24px]" img={assistantState.avatar_img} id={assistantState.avatar_color ? assistantState.avatar_color : assistantState.id} ><img src={assistantState.avatar_img ? assistantState.avatar_img : npcIcon} alt="" /></TitleIconBg>
|
||||
<span className="text-sm text-[#fff]">{t('build.debugPreview')}</span>
|
||||
</div>
|
||||
<ChatComponent
|
||||
|
||||
@@ -61,24 +61,19 @@ export default function editAssistant() {
|
||||
|
||||
// 上线助手
|
||||
const handleOnline = async () => {
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'success',
|
||||
description: t('skills.onlineSuccessful')
|
||||
if (!handleCheck()) return
|
||||
await handleSave()
|
||||
await captureAndAlertRequestErrorHoc(changeAssistantStatusApi(assistantState.id, 1)).then(res => {
|
||||
if (res === false) return
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'success',
|
||||
description: t('skills.onlineSuccessful')
|
||||
})
|
||||
})
|
||||
// if (!handleCheck()) return
|
||||
// await handleSave()
|
||||
// await captureAndAlertRequestErrorHoc(changeAssistantStatusApi(assistantState.id, 1)).then(res => {
|
||||
// if (res === false) return
|
||||
// message({
|
||||
// title: t('prompt'),
|
||||
// variant: 'success',
|
||||
// description: t('skills.onlineSuccessful')
|
||||
// })
|
||||
// })
|
||||
// setTimeout(() => {
|
||||
// navigate('/build')
|
||||
// }, 1200);
|
||||
setTimeout(() => {
|
||||
navigate('/build')
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
// 校验助手数据
|
||||
@@ -112,7 +107,7 @@ export default function editAssistant() {
|
||||
<div className="flex h-[calc(100vh-70px)]">
|
||||
<div className="w-[60%]">
|
||||
<div className="text-md font-medium leading-none p-4 shadow-sm text-[#fff] bg-[#000]">{t('build.assistantConfiguration')}</div>
|
||||
<div className="flex h-[calc(100vh-120px)]">
|
||||
<div className="flex h-[calc(100vh-106px)]">
|
||||
<Prompt></Prompt>
|
||||
<Setting></Setting>
|
||||
</div>
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function SkillPage() {
|
||||
<img src={create} className="w-[14px]" alt=""/>
|
||||
</div>}
|
||||
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => {setIsShareLink(!isShareLink);setIsShareLinkData("http://npcall.ai:3003/chat/" + item.id)}}>
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => {setIsShareLink(!isShareLink);setIsShareLinkData(location.origin + "/chat/" + item.id)}}>
|
||||
<img src={share} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@ import nengliIcon from "../../assets/npc/nengliIcon.png";
|
||||
import robot from "../../assets/robot.png";
|
||||
import { uploadFileWithProgress, uploadNpcHeaderLibFileWithProgress } from "../../modals/UploadModal/upload";
|
||||
import { TitleIconBg, gradients } from "@/components/bs-comp/cardComponent";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
|
||||
export default function l2Edit() {
|
||||
const { t } = useTranslation()
|
||||
@@ -32,16 +33,19 @@ export default function l2Edit() {
|
||||
const flow = useMemo(() => {
|
||||
return id ? nextFlow : null
|
||||
}, [nextFlow])
|
||||
console.log(flow,'flow')
|
||||
const [isL2, setIsL2] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const nameRef = useRef(null)
|
||||
const descRef = useRef(null)
|
||||
const guideRef = useRef(null)
|
||||
const [logo, setLogo] = useState("")
|
||||
const randomNum = Math.floor(Math.random()*(4-0+1)+0);
|
||||
const [avatar_img, setAvatar_img] = useState("")
|
||||
const [avatar_color, setAvatar_color] = useState("")
|
||||
const randomNum = Math.floor(Math.random()*(4-0+1)+0).toString();
|
||||
|
||||
useEffect(() => {
|
||||
if(!id){
|
||||
setAvatar_color(randomNum);
|
||||
}
|
||||
// 无id不再请求
|
||||
if (!id) return
|
||||
// 已有flow 数据时,不再请求
|
||||
@@ -50,7 +54,12 @@ export default function l2Edit() {
|
||||
nameRef.current.value = flow.name
|
||||
descRef.current.value = flow.description
|
||||
guideRef.current.value = flow.guide_word
|
||||
setLogo(flow.logo)
|
||||
setAvatar_img(flow.avatar_img);
|
||||
if(flow.avatar_color){
|
||||
setAvatar_color(flow.avatar_color);
|
||||
}else{
|
||||
setAvatar_color(id);
|
||||
}
|
||||
return
|
||||
}
|
||||
// 无flow从db获取
|
||||
@@ -61,14 +70,19 @@ export default function l2Edit() {
|
||||
nameRef.current.value = _flow.name
|
||||
descRef.current.value = _flow.description
|
||||
guideRef.current.value = _flow.guide_word
|
||||
setLogo(_flow.logo)
|
||||
setAvatar_img(_flow.avatar_img);
|
||||
if(_flow.avatar_color){
|
||||
setAvatar_color(_flow.avatar_color);
|
||||
}else{
|
||||
setAvatar_color(id);
|
||||
}
|
||||
})
|
||||
}, [id])
|
||||
|
||||
|
||||
// 校验
|
||||
const { user } = useContext(userContext);
|
||||
const [error, setError] = useState({ name: false, desc: false }) // 表单error信息展示
|
||||
// error信息展示
|
||||
const { message } = useToast()
|
||||
const isParamError = (name, desc, showErrorConfirm = false) => {
|
||||
const errorlist = [];
|
||||
if (!name) errorlist.push(t('skills.skillNameRequired'));
|
||||
@@ -78,9 +92,10 @@ export default function l2Edit() {
|
||||
const nameErrors = errorlist.length;
|
||||
if (!desc) errorlist.push(t('skills.skillDescRequired'));
|
||||
if (desc.length > 200) errorlist.push(t('skills.skillDescTooLong'));
|
||||
if (errorlist.length && showErrorConfirm) setErrorData({
|
||||
title: t('skills.errorTitle'),
|
||||
list: errorlist,
|
||||
if (errorlist.length && showErrorConfirm) message({
|
||||
title: t('prompt'),
|
||||
variant: 'error',
|
||||
description: errorlist
|
||||
});
|
||||
setError({ name: !!nameErrors, desc: errorlist.length > nameErrors });
|
||||
return !!errorlist.length;
|
||||
@@ -92,11 +107,8 @@ export default function l2Edit() {
|
||||
const name = nameRef.current.value
|
||||
const guideWords = guideRef.current.value
|
||||
const description = descRef.current.value
|
||||
const avatar_img = logo
|
||||
const avatar_color = gradients[randomNum]
|
||||
if (isParamError(name, description, true)) return
|
||||
setLoading(true)
|
||||
|
||||
await captureAndAlertRequestErrorHoc(createCustomFlowApi({
|
||||
name,
|
||||
description,
|
||||
@@ -124,7 +136,7 @@ export default function l2Edit() {
|
||||
setLoading(true)
|
||||
formRef.current?.save()
|
||||
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords}, true)
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords, avatar_img, avatar_color})
|
||||
setLoading(false)
|
||||
navigate('/flow/' + id, { replace: true })
|
||||
}
|
||||
@@ -133,14 +145,11 @@ export default function l2Edit() {
|
||||
const name = nameRef.current.value
|
||||
const description = descRef.current.value
|
||||
const guideWords = guideRef.current.value
|
||||
const avatar_img = logo
|
||||
const avatar_color = gradients[randomNum]
|
||||
// const logo = flow.logo
|
||||
if (isParamError(name, description)) return
|
||||
if (isParamError(name, description, true)) return
|
||||
setLoading(true)
|
||||
formRef.current?.save()
|
||||
console.log(flow,name,description,guideWords,logo)
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords, avatar_img, avatar_color}, true)
|
||||
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords, avatar_img, avatar_color})
|
||||
setLoading(false)
|
||||
setSuccessData({ title: t('success') });
|
||||
setTimeout(() => /^\/skill\/[\w\d-]+/.test(location.pathname) && navigate(-1), 2000);
|
||||
@@ -179,9 +188,10 @@ export default function l2Edit() {
|
||||
uploadNpcHeaderLibFileWithProgress(file, (progress) => { }).then(res => {
|
||||
// isSSO ? uploadFileWithProgress(file, (progress) => { }).then(res => {
|
||||
setLoading(false);
|
||||
if (typeof res === 'string') return setErrorData({ title: "Error", list: [res] })
|
||||
const { file_path } = res;
|
||||
setLogo(file_path);
|
||||
// console.log(res)
|
||||
// if (typeof res === 'string') return setErrorData({ title: "Error", list: [res] })
|
||||
// const { file_path } = res;
|
||||
setAvatar_img(res);
|
||||
// logo = file_path;
|
||||
// setMyValue(file.name);
|
||||
// onChange(file.name);
|
||||
@@ -220,12 +230,11 @@ export default function l2Edit() {
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
};
|
||||
|
||||
return <div className="relative box-border">
|
||||
<div className="p-6 pb-48 h-screen overflow-y-auto">
|
||||
<div className="flex justify-between w-full">
|
||||
<ShadTooltip content={t('back')} side="right">
|
||||
<button className="extra-side-bar-buttons w-[36px]" onClick={() => window.history.length < 3 ? navigate('/skills') : navigate(-1)}>
|
||||
<button className="extra-side-bar-buttons w-[36px]" onClick={() => window.history.length < 3 ? navigate('/build/skills') : navigate(-1)}>
|
||||
<ArrowLeft strokeWidth={1.5} className="side-bar-button-size" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
@@ -246,9 +255,11 @@ export default function l2Edit() {
|
||||
<div className="pt-[20px] pr-[14px] pl-[14px]">
|
||||
<p>能力头像</p>
|
||||
<div className="flex items-center ml-[7px] mt-[10px]">
|
||||
{!logo ? <TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" id={randomNum} ><img onClick={handleButtonClick} src={nengliIcon} alt="" /></TitleIconBg> : <img src={logo} className="w-[41px] h-[41px]" onClick={handleButtonClick} alt="" />}
|
||||
|
||||
<div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setLogo(robot)}>
|
||||
{/* {flow && avatar_img ? <img src={flow.avatar_img} className="w-[41px] h-[41px] cursor-pointer" style={{borderRadius:"7px"}} onClick={handleButtonClick} alt="" /> : <TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" img={avatar_img} id={avatar_color} ><img onClick={handleButtonClick} src={nengliIcon} alt="" /></TitleIconBg>} */}
|
||||
{/* {flow && !flow.avatar_img && flow.avatar_color ? <TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" id={randomNum} ><img onClick={handleButtonClick} src={nengliIcon} alt="" /></TitleIconBg> : <img src={logo} className="w-[41px] h-[41px]" onClick={handleButtonClick} alt="" />} */}
|
||||
<TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" img={avatar_img} id={avatar_color} ><img onClick={handleButtonClick} src={flow && avatar_img ? avatar_img : nengliIcon} alt="" /></TitleIconBg>
|
||||
|
||||
<div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setAvatar_img("")}>
|
||||
<img src={huifumoren} className="w-[12px] h-[11px]" alt="" />
|
||||
<span className="ml-[5px] text-[#999999] text-[12px] mt-[1px]">恢复默认</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CardComponent from "../../components/bs-comp/cardComponent";
|
||||
import CardComponent, { TitleIconBg } from "../../components/bs-comp/cardComponent";
|
||||
import { Dialog, DialogTrigger } from "../../components/bs-ui/dialog";
|
||||
import { SearchInput } from "../../components/bs-ui/input";
|
||||
import AutoPagination from "../../components/bs-ui/pagination/autoPagination";
|
||||
@@ -30,6 +30,8 @@ import { updataOnlineState } from "@/controllers/API/flow";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { userContext } from "@/contexts/userContext";
|
||||
import ShareLink from "./externalUse/shareLink";
|
||||
import npcIcon from "../../assets/npc/npcIcon.png";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function Assistants() {
|
||||
const { t } = useTranslation()
|
||||
@@ -49,15 +51,19 @@ export default function Assistants() {
|
||||
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const handleDelete = (data) => {
|
||||
bsConfirm({
|
||||
desc: t('确认删除该助手?'),
|
||||
okTxt: t('delete'),
|
||||
onOk(next) {
|
||||
deleteAssistantApi(data.id).then(() => reload())
|
||||
next()
|
||||
}
|
||||
})
|
||||
const handleDelete = () => {
|
||||
// bsConfirm({
|
||||
// desc: t('确认删除该NPC?'),
|
||||
// okTxt: t('delete'),
|
||||
// onOk(next) {
|
||||
// deleteAssistantApi(data.id).then(() => reload())
|
||||
// next()
|
||||
// }
|
||||
// })
|
||||
deleteAssistantApi(idRef.current.id).then(res => {
|
||||
reload();
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
const handleCheckedChange = (checked, data) => {
|
||||
@@ -69,6 +75,14 @@ export default function Assistants() {
|
||||
})
|
||||
}
|
||||
|
||||
const checkSassUrl = (url: string) => {
|
||||
console.log(location.origin)
|
||||
// return url;
|
||||
// return url.replace(/https?:\/\/[^\/]+/, '')
|
||||
// location.origin === SASS_HOST ? url.replace(/https?:\/\/[^\/]+/, '') : url;
|
||||
}
|
||||
const { delShow, idRef, close, delConfirm } = useDelete();
|
||||
|
||||
const render = (item: any) => {
|
||||
const [openSwitch, setOpenSwitch] = useState(item.status === 1);
|
||||
const handleChange = (bln) => {
|
||||
@@ -94,8 +108,8 @@ export default function Assistants() {
|
||||
<img src={zidingyijia} alt="" />
|
||||
<span>自定义NPC</span>
|
||||
</div>
|
||||
<p>通过描述角色和任务来创建你的NPC,NPC 可以调用多个能力和工具。</p>
|
||||
<div className="flex justify-end mb-[7px] mr-[14px] mt-0">
|
||||
<p>通过描述角色和任务来创建你的NPC,NPC可以调用多个能力和工具。</p>
|
||||
<div className="absolute flex justify-end bottom-[7px] right-[14px] mt-0">
|
||||
<img src={zidingyi1} className="w-[68px]" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,17 +134,13 @@ export default function Assistants() {
|
||||
{openSwitch && <span>
|
||||
<span>
|
||||
<div>
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[160px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[160px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[160px]" alt=""/>}
|
||||
<TitleIconBg className="w-[160px] h-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : npcIcon} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>}
|
||||
</div>
|
||||
<div>
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[40px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[40px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[40px]" alt=""/>}
|
||||
<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 : npcIcon} alt="" /></TitleIconBg>
|
||||
{/* <img src={robot} className="w-[40px]" alt=""/> */}
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
</div>
|
||||
@@ -142,12 +152,7 @@ export default function Assistants() {
|
||||
编辑
|
||||
</div>}
|
||||
|
||||
|
||||
<div className="w-[27px] h-[27px] create mr-[14px]" onClick={() => {setIsShareLink(!isShareLink);setIsShareLinkData("http://npcall.ai:3003/chat/" + item.id)}}>
|
||||
<img src={share} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => handleDelete(item)}>
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,5 +234,33 @@ export default function Assistants() {
|
||||
</Dialog>
|
||||
{/* 分享链接 */}
|
||||
<ShareLink isShareLink={isShareLink} setIsShareLink={setIsShareLink} data={isShareLinkData}></ShareLink>
|
||||
|
||||
<dialog className={`modal ${delShow && 'modal-open'}`}>
|
||||
<form method="dialog" className="modal-box w-[400px] bg-[#262626] shadow-lg">
|
||||
<h3 className="text-[16px] font-bold text-center" style={{color:"#FFFFFF"}}>{t('prompt')}</h3>
|
||||
<p className="text-[12px] text-center mt-[18px]" style={{color:"#FFFFFF"}}>确认删除该NPC? 删除后数据无法恢复,请确认!</p>
|
||||
<div className="flex justify-center mt-[27px]">
|
||||
<Button className="baogao-btn" variant="outline" onClick={close}>{t('cancel')}</Button>
|
||||
<Button className="baogao-btn ml-[27px]" variant="destructive" onClick={handleDelete}>{t('delete')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
};
|
||||
};
|
||||
|
||||
const useDelete = () => {
|
||||
const [delShow, setDelShow] = useState(false)
|
||||
const idRef = useRef<any>(null)
|
||||
|
||||
return {
|
||||
delShow,
|
||||
idRef,
|
||||
close: () => {
|
||||
setDelShow(false)
|
||||
},
|
||||
delConfirm: (id) => {
|
||||
idRef.current = id
|
||||
setDelShow(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CardComponent from "../../components/bs-comp/cardComponent";
|
||||
import CardComponent, { TitleIconBg } from "../../components/bs-comp/cardComponent";
|
||||
import { MoveOneIcon } from "../../components/bs-icons/moveOne";
|
||||
import { Button } from "../../components/bs-ui/button";
|
||||
import { SearchInput } from "../../components/bs-ui/input";
|
||||
@@ -28,7 +28,7 @@ import share from "../../assets/npc/share.png";
|
||||
import moban from "../../assets/npc/moban.png";
|
||||
import sousuo from "../../assets/npc/sousuo.png";
|
||||
import del from "../../assets/npc/del.png";
|
||||
import zidingyi1 from "../../assets/npc/zidingyi1.png";
|
||||
import zidingyi1 from "../../assets/npc/nengliIcon1.png";
|
||||
import zidingyijia from "../../assets/npc/zidingyijia.png";
|
||||
import ShareLink from "./externalUse/shareLink";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
@@ -36,6 +36,7 @@ import { Flexbox } from 'react-layout-kit';
|
||||
import Templates from "./temps";
|
||||
import { Size } from "recharts/types/util/types";
|
||||
import SkillTemps from "./components/SkillTemps";
|
||||
import nengliIcon from "../../assets/npc/nengliIcon.png";
|
||||
|
||||
export default function Skills() {
|
||||
const { t } = useTranslation()
|
||||
@@ -72,14 +73,18 @@ export default function Skills() {
|
||||
}
|
||||
|
||||
const handleDelete = (data) => {
|
||||
bsConfirm({
|
||||
desc: t('skills.confirmDeleteSkill'),
|
||||
okTxt: t('delete'),
|
||||
onOk(next) {
|
||||
captureAndAlertRequestErrorHoc(deleteFlowFromDatabase(data.id).then(reload));
|
||||
next()
|
||||
}
|
||||
})
|
||||
// bsConfirm({
|
||||
// desc: t('skills.confirmDeleteSkill'),
|
||||
// okTxt: t('delete'),
|
||||
// onOk(next) {
|
||||
// captureAndAlertRequestErrorHoc(deleteFlowFromDatabase(data.id).then(reload));
|
||||
// next()
|
||||
// }
|
||||
// })
|
||||
captureAndAlertRequestErrorHoc(deleteFlowFromDatabase(idRef.current.id).then(res => {
|
||||
reload();
|
||||
close();
|
||||
}));
|
||||
}
|
||||
|
||||
const handleSetting = (data) => {
|
||||
@@ -107,6 +112,7 @@ export default function Skills() {
|
||||
window.SearchSkillsPage = { no: pageNo, key: inputRef.current.value };
|
||||
nvaigate("/skill/" + id)
|
||||
}
|
||||
const { delShow, idRef, close, delConfirm } = useDelete();
|
||||
|
||||
// 模板管理
|
||||
if (isTempsPage) return <Templates onBack={() => setIsTempPage(false)} onChange={loadTemps}></Templates>
|
||||
@@ -132,27 +138,25 @@ export default function Skills() {
|
||||
<img src={zidingyijia} alt="" />
|
||||
<span>自定义能力</span>
|
||||
</div>
|
||||
<p>能力通过可视化的流程编排,明确工作执行 步骤。我们提供场景模板供您使用和参考。</p>
|
||||
<img src={zidingyi1} alt="" />
|
||||
<p>能力通过可视化的流程编排,明确工作执行步骤。我们提供场景模板供您使用和参考。</p>
|
||||
<div className="absolute flex justify-end bottom-[7px] right-[14px] mt-0">
|
||||
<img src={zidingyi1} className="w-[68px]" alt="" />
|
||||
</div>
|
||||
</div>:
|
||||
<div className={`selectNpcFlexbox`}>
|
||||
<div className="npcInfoItemBg">
|
||||
{openSwitch && <span>
|
||||
<span>
|
||||
<div>
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[160px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[160px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[160px]" alt=""/>}
|
||||
<TitleIconBg className="w-[160px] h-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : nengliIcon} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>}
|
||||
</div>
|
||||
<div>
|
||||
{(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[40px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[40px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[40px]" alt=""/>}
|
||||
<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 : nengliIcon} alt="" /></TitleIconBg>
|
||||
{/* <img src={robot} className="w-[40px]" alt=""/> */}
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
<p className="ml-[14px] yichu">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.description}</p>
|
||||
<div>
|
||||
@@ -166,16 +170,23 @@ export default function Skills() {
|
||||
<img src={create} className="w-[14px]" alt=""/>
|
||||
</div>}
|
||||
|
||||
<div className="w-[27px] h-[27px] create mr-[14px]" onClick={() => {setIsShareLink(!isShareLink);setIsShareLinkData("http://npcall.ai:3003/chat/" + item.id)}}>
|
||||
{openSwitch && <div className="w-[27px] h-[27px] create mr-[14px]" onClick={() => {setIsShareLink(!isShareLink);setIsShareLinkData(location.origin + "/chat/" + item.id)}}>
|
||||
<img src={share} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => handleDelete(item)}>
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => delConfirm(item)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch checked={openSwitch} onCheckedChange={handleChange} />
|
||||
</div>
|
||||
<div className="absolute right-[7px] top-[7px]">
|
||||
<CardSelectVersion
|
||||
showPop={item.status !== 2}
|
||||
data={item}
|
||||
></CardSelectVersion>
|
||||
</div>
|
||||
|
||||
</div>}
|
||||
</Flexbox>
|
||||
)
|
||||
@@ -260,6 +271,17 @@ export default function Skills() {
|
||||
</div>
|
||||
{/* 分享链接 */}
|
||||
<ShareLink isShareLink={isShareLink} setIsShareLink={setIsShareLink} data={isShareLinkData}></ShareLink>
|
||||
|
||||
<dialog className={`modal ${delShow && 'modal-open'}`}>
|
||||
<form method="dialog" className="modal-box w-[400px] bg-[#262626] shadow-lg">
|
||||
<h3 className="text-[16px] font-bold text-center" style={{color:"#FFFFFF"}}>{t('prompt')}</h3>
|
||||
<p className="text-[12px] text-center mt-[18px]" style={{color:"#FFFFFF"}}>确认删除该能力? 删除后数据无法恢复,请确认!</p>
|
||||
<div className="flex justify-center mt-[27px]">
|
||||
<Button className="baogao-btn" variant="outline" onClick={close}>{t('cancel')}</Button>
|
||||
<Button className="baogao-btn ml-[27px]" variant="destructive" onClick={handleDelete}>{t('delete')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -291,4 +313,21 @@ const useTemps = () => {
|
||||
}, []);
|
||||
|
||||
return [temps, loadTemps];
|
||||
};
|
||||
};
|
||||
|
||||
const useDelete = () => {
|
||||
const [delShow, setDelShow] = useState(false)
|
||||
const idRef = useRef<any>(null)
|
||||
|
||||
return {
|
||||
delShow,
|
||||
idRef,
|
||||
close: () => {
|
||||
setDelShow(false)
|
||||
},
|
||||
delConfirm: (id) => {
|
||||
idRef.current = id
|
||||
setDelShow(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,8 @@ export default function tabTools({ select = null, onSelect }) {
|
||||
>
|
||||
{type === "" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <PersonIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">内置工具</span>
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "" && "text-[#FFD025]"
|
||||
}`}>内置工具</span>
|
||||
</div>
|
||||
<div
|
||||
className={`flex cursor-pointer items-center w-[237px] h-[40px] ml-[14px] border-radius-7 pl-[14px] ${type === "edit" && "bg-[#2A271D] text-[#FFD54C]"
|
||||
@@ -82,7 +83,8 @@ export default function tabTools({ select = null, onSelect }) {
|
||||
>
|
||||
{type === "edit" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <StarFilledIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">自定义工具</span>
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "edit" && "text-[#FFD025]"
|
||||
}`}>自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Navigate, createBrowserRouter } from "react-router-dom";
|
||||
import MainLayout from "./layout/MainLayout";
|
||||
import FileLibPage from "./pages/FileLibPage";
|
||||
// import FileLibPage from "./pages/Knowledge";
|
||||
// import FilesPage from "./pages/FileLibPage/files";
|
||||
import FilesPage from "./pages/Knowledge/knowledge";
|
||||
import FilesPage from "./pages/FileLibPage/files";
|
||||
// import FilesPage from "./pages/Knowledge/knowledge";
|
||||
import FlowPage from "./pages/FlowPage";
|
||||
import ModelPage from "./pages/ModelPage";
|
||||
import Doc from "./pages/ModelPage/doc";
|
||||
|
||||
@@ -33,7 +33,7 @@ const assistantReducer = (state: State, action: Action, data: Partial<AssistantD
|
||||
|
||||
|
||||
const assistantTemp = {
|
||||
id: 3,
|
||||
id: "",
|
||||
name: "",
|
||||
desc: "",
|
||||
logo: "",
|
||||
@@ -49,6 +49,8 @@ const assistantTemp = {
|
||||
tool_list: [],
|
||||
flow_list: [],
|
||||
knowledge_list: [],
|
||||
avatar_img: "",
|
||||
avatar_color: "",
|
||||
}
|
||||
|
||||
export const useAssistantStore = create<State & Actions>((set) => ({
|
||||
|
||||
@@ -84,6 +84,15 @@
|
||||
background-size: 30px 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.xinDuiHua-boxR{
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
background-image: url('../assets/npc/border-r.png');
|
||||
background-size: 30px 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
opacity: 90%;
|
||||
}
|
||||
.xinDuiHua{
|
||||
display: flex;
|
||||
padding-top: 21px;
|
||||
@@ -183,10 +192,24 @@
|
||||
}
|
||||
}
|
||||
.duihua-chat{
|
||||
width: calc(100% - 288px);
|
||||
height: 100%;
|
||||
background-image: url('../assets/chat/duihua-bg.png');
|
||||
background-size: 30px 100%;
|
||||
background-repeat: no-repeat;
|
||||
// background-image: url('../assets/chat/duihua-bg.png');
|
||||
// background-size: 30px 100%;
|
||||
// background-repeat: no-repeat;
|
||||
|
||||
}
|
||||
.chatShare{
|
||||
.duihua-chat{
|
||||
width: 100%!important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.chatShareM{
|
||||
.duihua-chat{
|
||||
width: 100%!important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.duihua-chat-top{
|
||||
width: 100%;
|
||||
@@ -195,6 +218,19 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(13, 13, 13, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
// opacity: 0.8;
|
||||
// &::before{
|
||||
// content: "";
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// position: absolute;
|
||||
// background: rgba(13, 13, 13, 0.5);
|
||||
// backdrop-filter: blur(20px);
|
||||
// opacity: 0.8;
|
||||
// }
|
||||
>div:nth-of-type(1){
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -612,9 +648,8 @@
|
||||
border: 1px solid #FFD025;
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
height: 127px;
|
||||
display: block;
|
||||
/* padding: 14px; */
|
||||
padding-bottom: 14px;
|
||||
>div:nth-of-type(2){
|
||||
display: flex;
|
||||
align-items: self-start;
|
||||
@@ -2602,7 +2637,8 @@
|
||||
}
|
||||
}
|
||||
.selectNpcFlexboxZiDing{
|
||||
height: 176px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
>div{
|
||||
margin-left: 14px;
|
||||
margin-top: 27px;
|
||||
@@ -2635,12 +2671,14 @@
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
width: 100%;
|
||||
height: 176px;
|
||||
height: 100%;
|
||||
min-height: 180px;
|
||||
// height: 190px;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content:space-between;
|
||||
/* padding: 14px; */
|
||||
position: relative;
|
||||
// position: relative;
|
||||
padding-bottom: 55px;
|
||||
>div:nth-of-type(2){
|
||||
display: flex;
|
||||
@@ -2659,12 +2697,13 @@
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: #CCCCCC;
|
||||
margin-top: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
>div:nth-of-type(3){
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
@@ -3353,7 +3392,7 @@
|
||||
}
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
// height: 127px;
|
||||
height: 127px;
|
||||
padding-bottom: 14px;
|
||||
display: block;
|
||||
/* padding: 14px; */
|
||||
@@ -3921,23 +3960,20 @@
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
.bs-chat-bg{
|
||||
.questionTextarea{
|
||||
width: 95%!important;
|
||||
margin-left: 2.5%;
|
||||
height: 40px!important;
|
||||
background: #1A1A1A!important;
|
||||
box-shadow: 0px 2px 7px 0px rgba(0,1,51,0.15)!important;
|
||||
border-radius: 20px!important;
|
||||
padding: 10px 0;
|
||||
padding-left: 14px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 11px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.questionTextarea{
|
||||
width: 95%!important;
|
||||
margin-left: 2.5%;
|
||||
height: 40px!important;
|
||||
background: #1A1A1A!important;
|
||||
box-shadow: 0px 2px 7px 0px rgba(0,1,51,0.15)!important;
|
||||
border-radius: 20px!important;
|
||||
padding: 10px 0;
|
||||
padding-left: 14px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 11px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.skillSheet{
|
||||
height: 100%;
|
||||
padding: 0 27px 30px 27px;
|
||||
@@ -4090,4 +4126,29 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.SelectTrigger{
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 14px!important;
|
||||
border: none;
|
||||
&:focus{
|
||||
// border: 1px solid #997e1f!important;
|
||||
border: none!important;
|
||||
outline: none!important;
|
||||
}
|
||||
}
|
||||
.yichu{
|
||||
width: calc(100% - 110px); /* 定义容器宽度 */
|
||||
white-space: nowrap; /* 确保文本在一行内显示 */
|
||||
overflow: hidden; /* 隐藏溢出的内容 */
|
||||
text-overflow: ellipsis; /* 使用省略号表示文本溢出 */
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export interface AssistantDetail {
|
||||
flow_list?: FlowType[];
|
||||
/** 知识库ID列表,为None则不更新 */
|
||||
knowledge_list?: { id: number, name: string, index_name: string }[];
|
||||
avatar_img?: string;
|
||||
avatar_color?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||