1
BIN
src/.DS_Store
vendored
@@ -208,3 +208,9 @@ path.react-flow__edge-interaction:hover {
|
||||
z-index: 99;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bs-chat-bg {
|
||||
width: 100%;
|
||||
background: url(/points.png) 0 100% repeat-x;
|
||||
background-size: 10px 432px;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import "./App.css";
|
||||
import ErrorAlert from "./alerts/error";
|
||||
import NoticeAlert from "./alerts/notice";
|
||||
import SuccessAlert from "./alerts/success";
|
||||
import { Toaster } from "./components/bs-ui/toast";
|
||||
import { alertContext } from "./contexts/alertContext";
|
||||
import { locationContext } from "./contexts/locationContext";
|
||||
import { userContext } from "./contexts/userContext";
|
||||
@@ -215,6 +216,8 @@ export default function App() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 新弹窗 */}
|
||||
<Toaster></Toaster>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BIN
src/assets/.DS_Store
vendored
BIN
src/assets/npc/.DS_Store
vendored
BIN
src/assets/npc/gongjuAdd.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/npc/gongjuBianji.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/npc/gongjuIcon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/npc/gongjuIcon1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/npc/gongjuIcon3.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/npc/nengliIcon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/npc/npcIcon.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/npc/zidingyijia.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/npc/矩形 1 拷贝 8@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/components/.DS_Store
vendored
Normal file
@@ -35,11 +35,11 @@ interface IProps<T> {
|
||||
}
|
||||
|
||||
export const gradients = [
|
||||
'bg-amber-500',
|
||||
'bg-orange-600',
|
||||
'bg-teal-500',
|
||||
'bg-purple-600',
|
||||
'bg-blue-700'
|
||||
'bg-[#FF9B25]',
|
||||
'bg-[#04BCD2]',
|
||||
'bg-[#2586FF]',
|
||||
'bg-[#D855D3]',
|
||||
'bg-[#C04B51]'
|
||||
]
|
||||
|
||||
// 'bg-slate-600',
|
||||
@@ -61,7 +61,7 @@ export const gradients = [
|
||||
// 'bg-pink-600',
|
||||
// 'bg-rose-600'
|
||||
export function TitleIconBg({ id, className = '', children = <SkillIcon /> }) {
|
||||
return <div className={cname(`rounded-sm flex justify-center items-center ${gradients[parseInt(id + '', 16) % gradients.length]}`, className)}>{children}</div>
|
||||
return <div className={cname(`flex justify-center items-center cursor-pointer ${gradients[parseInt(id + '', 16) % gradients.length]}`, className)} style={{borderRadius:"7px"}}>{children}</div>
|
||||
}
|
||||
|
||||
export default function CardComponent<T>({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
import GuideQuestions from "./GuideQuestions";
|
||||
import { ClearIcon } from "@/components/bs-icons/clear";
|
||||
import duihua_send from "../../../assets/chat/duihua-send.png";
|
||||
|
||||
export default function ChatInput({ clear, form, questions, inputForm, wsUrl, onBeforSend }) {
|
||||
const { toast } = useToast()
|
||||
@@ -230,8 +231,8 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
// setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
|
||||
return <div className="absolute bottom-0 w-full pt-1 bg-[#fff] dark:bg-[#1B1B1B]">
|
||||
<div className={`relative ${clear && 'pl-9'}`}>
|
||||
return <div className="absolute bottom-0 w-full bg-[#fff] dark:bg-[#000000]">
|
||||
<div className={`relative`}>
|
||||
{/* form */}
|
||||
{
|
||||
formShow && <div className="relative">
|
||||
@@ -248,14 +249,14 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
onClick={handleClickGuideWord}
|
||||
/>
|
||||
{/* clear */}
|
||||
<div className="flex absolute left-0 top-4 z-10">
|
||||
{/* <div className="flex absolute left-0 top-4 z-10">
|
||||
{
|
||||
clear && <div
|
||||
className={`w-6 h-6 rounded-sm hover:bg-gray-200 cursor-pointer flex justify-center items-center `}
|
||||
onClick={() => { !inputLock.locked && destory() }}
|
||||
><ClearIcon className={!showWhenLocked && inputLock.locked ? 'text-gray-400' : 'text-gray-950'} ></ClearIcon></div>
|
||||
}
|
||||
</div>
|
||||
</div> */}
|
||||
{/* form */}
|
||||
<div className="flex absolute left-3 top-4 z-10">
|
||||
{
|
||||
@@ -266,30 +267,36 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
}
|
||||
</div>
|
||||
{/* send */}
|
||||
<div className="flex gap-2 absolute right-3 top-4 z-10">
|
||||
<div className="flex gap-2 absolute right-[2.5%] z-10">
|
||||
<div
|
||||
id="bs-send-btn"
|
||||
className="w-6 h-6 rounded-sm hover:bg-gray-200 cursor-pointer flex justify-center items-center"
|
||||
className="w-[68px] h-[40px] bg-[#FFD54C] cursor-pointer flex justify-center items-center"
|
||||
onClick={() => { !inputLock.locked && handleSendClick() }}
|
||||
><SendIcon className={inputLock.locked ? 'text-gray-400' : 'text-gray-950'}></SendIcon></div>
|
||||
style={{borderRadius:"20px"}}
|
||||
>
|
||||
{/* <SendIcon className={inputLock.locked ? 'text-gray-400' : 'text-gray-950'}></SendIcon> */}
|
||||
<img src={duihua_send} className="w-[20px]" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
{/* question */}
|
||||
<Textarea
|
||||
<textarea
|
||||
id="bs-send-input"
|
||||
ref={inputRef}
|
||||
rows={1}
|
||||
style={{ height: 56 }}
|
||||
style={{ height: 34 }}
|
||||
disabled={inputLock.locked}
|
||||
onInput={handleTextAreaHeight}
|
||||
placeholder={inputLock.locked ? inputLock.reason : t('chat.inputPlaceholder')}
|
||||
className={"resize-none py-4 pr-10 text-md min-h-6 max-h-[200px] scrollbar-hide dark:bg-[#2A2B2E] text-gray-800" + (form && ' pl-10')}
|
||||
// className={"resize-none py-4 pr-10 text-md min-h-6 max-h-[200px] scrollbar-hide dark:bg-[#2A2B2E] text-gray-800" + (form && ' pl-10')}
|
||||
className="questionTextarea w-full resize-none border-none bg-transparent outline-none max-h-[160px]"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
!inputLock.locked && handleSendClick()
|
||||
}
|
||||
}}
|
||||
></Textarea>
|
||||
></textarea>
|
||||
<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>
|
||||
|
||||
@@ -27,13 +27,13 @@ export default function GuideQuestions({ locked, chatId, questions, onClick }) {
|
||||
if (locked || !words.length) return null
|
||||
|
||||
if (showGuideQuestion) return <div className="relative">
|
||||
<div className="absolute left-0 bottom-0">
|
||||
<p className="text-gray-950 text-sm mb-2 bg-[rgba(255,255,255,0.8)] rounded-md w-fit px-2 py-1">{t('chat.recommendationQuestions')}</p>
|
||||
<div className="absolute left-[14px] bottom-0">
|
||||
<p className="text-[#fff] mb-[10px] bg-[]">推荐问题</p>
|
||||
{
|
||||
words.map((question, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-fit bg-[#d4dffa] border-2 border-gray-50 shadow-md text-gray-600 rounded-md mb-1 px-4 py-1 text-sm cursor-pointer"
|
||||
className="w-fit bg-[#52430c] shadow-md text-[#fff] rounded-md mb-1 px-4 py-1 text-sm cursor-pointer"
|
||||
onClick={() => {
|
||||
setShowGuideQuestion(false)
|
||||
onClick(question)
|
||||
|
||||
@@ -80,7 +80,7 @@ export default function MessageBs({ data, onUnlike = () => { }, onSource }: { da
|
||||
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="min-h-8 px-6 py-4 rounded-2xl bg-[#F5F6F8] dark:bg-[#313336]">
|
||||
<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="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
|
||||
{data.message.toString() ?
|
||||
|
||||
@@ -27,7 +27,7 @@ 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%]">
|
||||
{useName && <p className="text-gray-600 text-xs mb-2 text-right">{useName}</p>}
|
||||
<div className="rounded-2xl px-6 py-4 bg-[#EEF2FF] dark:bg-[#333A48]">
|
||||
<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>
|
||||
@@ -36,7 +36,7 @@ export default function MessageUser({ useName, data }: { data: ChatMessageType }
|
||||
{/* 附加信息 */}
|
||||
{
|
||||
// 数组类型的 data通常是文件上传消息,不展示附加按钮
|
||||
!Array.isArray(data.message.data) && <div className="flex justify-between mt-2">
|
||||
!Array.isArray(data.message.data) && <div className="flex justify-between mt-2 mr-[14px]">
|
||||
<span></span>
|
||||
<div className="flex gap-2 text-gray-400 cursor-pointer self-end">
|
||||
{!running && <Pencil2Icon className="hover:text-gray-500" onClick={() => handleResend(false)} />}
|
||||
|
||||
@@ -12,6 +12,13 @@ import {
|
||||
} from "../../bs-ui/sheet";
|
||||
import CardComponent from "../cardComponent";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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";
|
||||
|
||||
export default function SkillSheet({ select, children, onSelect }) {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
@@ -24,7 +31,6 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const { value } = e.target;
|
||||
setKeyword(value);
|
||||
@@ -37,12 +43,73 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
||||
const render = (item: any) => (
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox`}>
|
||||
{/* <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=""/> */}
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
<div>
|
||||
{/* <div>绘画类</div>
|
||||
<div>绘画类</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-[10px] test-[13px]">{item.description}</p>
|
||||
<div className="flex justify-end mb-[14px] mr-[14px] mt-[14px]">
|
||||
{select.some((_) => _.id === item.id) ? (
|
||||
<Button size="sm" className="h-6 bg-[#FFD025]" disabled>
|
||||
{t("build.added")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6 bg-[#FFD025]"
|
||||
onClick={() => onSelect(item)}
|
||||
>
|
||||
{t("build.add")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>{children}</SheetTrigger>
|
||||
<SheetContent className="bg-gray-100 sm:min-w-[966px]">
|
||||
<SheetContent className="bg-[#1A1A1A] sm:min-w-[966px]">
|
||||
<div className="flex h-full" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<div className="w-[270px] p-6">
|
||||
<SheetTitle>{t("build.addSkill")}</SheetTitle>
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
@@ -50,47 +117,47 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
className="my-6"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Button className="w-full" onClick={toCreateFlow}>
|
||||
{/* <Button className="w-full" onClick={toCreateFlow}>
|
||||
{t("build.createSkill")}
|
||||
</Button>
|
||||
</Button> */}
|
||||
<div className="w-[244px] h-[34px] flex items-center justify-center bg-[#FFD025] text-[#000] cursor-pointer" style={{borderRadius:"34px"}} onClick={toCreateFlow}>{t("build.createSkill")}</div>
|
||||
</div>
|
||||
<div className="flex h-full min-w-[696px] flex-1 flex-wrap content-start gap-1.5 overflow-y-auto bg-[#fff] p-5 pt-12 scrollbar-hide">
|
||||
<div className="min-w-[696px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
|
||||
{onlineFlows[0] ? (
|
||||
onlineFlows.map((flow, i) => (
|
||||
<CardComponent
|
||||
key={i}
|
||||
id={i + 1}
|
||||
data={flow}
|
||||
title={flow.name}
|
||||
description={flow.description}
|
||||
type="sheet"
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
{select.some((_) => _.id === flow.id) ? (
|
||||
<Button size="sm" className="h-6" disabled>
|
||||
{t("build.added")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6"
|
||||
onClick={() => onSelect(flow)}
|
||||
>
|
||||
{t("build.add")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))
|
||||
// onlineFlows.map((flow, i) => (
|
||||
// <CardComponent
|
||||
// key={i}
|
||||
// id={i + 1}
|
||||
// data={flow}
|
||||
// title={flow.name}
|
||||
// description={flow.description}
|
||||
// type="sheet"
|
||||
// footer={
|
||||
// <div className="flex justify-end">
|
||||
// {select.some((_) => _.id === flow.id) ? (
|
||||
// <Button size="sm" className="h-6" disabled>
|
||||
// {t("build.added")}
|
||||
// </Button>
|
||||
// ) : (
|
||||
// <Button
|
||||
// size="sm"
|
||||
// className="h-6"
|
||||
// onClick={() => onSelect(flow)}
|
||||
// >
|
||||
// {t("build.add")}
|
||||
// </Button>
|
||||
// )}
|
||||
// </div>
|
||||
// }
|
||||
// />
|
||||
// ))
|
||||
<SpotlightCard items={onlineFlows} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
|
||||
) : (
|
||||
<div className="flex w-full flex-col items-center justify-center pt-40">
|
||||
<p className="mb-3 text-sm text-muted-foreground">
|
||||
<p className="mb-3 text-sm text-[#fff]">
|
||||
{t("build.empty")}
|
||||
</p>
|
||||
<Button className="w-[200px]" onClick={toCreateFlow}>
|
||||
{t("build.createSkill")}
|
||||
</Button>
|
||||
<div className="w-[200px] h-[34px] flex items-center justify-center bg-[#FFD025] text-[#000] cursor-pointer" style={{borderRadius:"34px"}} onClick={toCreateFlow}>{t("build.createSkill")}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { PersonIcon, StarFilledIcon } from "@radix-ui/react-icons";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
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";
|
||||
|
||||
export default function ToolsSheet({ select, onSelect, children }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -34,35 +38,48 @@ export default function ToolsSheet({ select, onSelect, children }) {
|
||||
<SheetTrigger asChild>
|
||||
{children}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-gray-100">
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-[#121212]">
|
||||
<div className="flex h-full" onClick={e => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<SheetTitle>{t('build.addTool')}</SheetTitle>
|
||||
<SearchInput placeholder={t('build.search')} className="mt-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
<Button
|
||||
<div className="relative mt-[14px]">
|
||||
<img src={sousuo} className="absolute w-[14px] left-[14px] top-[10px]" alt="" />
|
||||
<input placeholder="搜索"
|
||||
className="w-[237px] h-[34px] bg-[#1A1A1A] text-[#fff] pl-[40px]"
|
||||
style={{borderRadius:"17px",outline:"none"}}
|
||||
onChange={(e) => setKeyword(e.target.value)} type="text" />
|
||||
</div>
|
||||
{/* <SearchInput placeholder={t('build.search')} className="mt-6" onChange={(e) => setKeyword(e.target.value)} /> */}
|
||||
{/* <Button
|
||||
className="mt-4 w-full"
|
||||
onClick={() => window.open("/build/tools")}
|
||||
>
|
||||
{t('create')}{t("tools.createCustomTool")}
|
||||
</Button>
|
||||
</Button> */}
|
||||
<div className="w-[237px] h-[27px] bg-[#FFD025] mt-[14px] flex justify-center items-center border-radius-14 cursor-pointer" onClick={() => window.open("/build/tools")}>
|
||||
<img src={gongjuAdd} className="w-[14px]" alt="" />
|
||||
<span className="text-[#333333] ml-[12px]">自定义工具</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 ${type === 'default' && 'bg-muted-foreground/10'}`}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 ${type === 'default' && 'bg-[#2A271D] text-[#FFD54C]'}`}
|
||||
onClick={() => setType('default')}
|
||||
>
|
||||
<PersonIcon />
|
||||
<span>{t('tools.builtinTools')}</span>
|
||||
{/* <PersonIcon /> */}
|
||||
{type === "default" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
<span className="ml-[8px] text-[#999999]">内置工具</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-muted-foreground/10'}`}
|
||||
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]'}`}
|
||||
onClick={() => setType('custom')}
|
||||
>
|
||||
<StarFilledIcon />
|
||||
<span>{t('tools.customTools')}</span>
|
||||
{type === "custom" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <StarFilledIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 bg-[#fff] p-5 pt-12 h-full overflow-auto scrollbar-hide">
|
||||
<div className="flex-1 bg-[#121212] p-5 pt-12 h-full overflow-auto scrollbar-hide">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{
|
||||
options.length ? options.map(el => (
|
||||
|
||||
BIN
src/components/bs-ui/.DS_Store
vendored
Normal file
@@ -32,7 +32,7 @@ const AccordionTrigger = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronRightIcon color="#111" className="mx-2 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||
<ChevronRightIcon color="#fff" className="mx-2 h-4 w-4 shrink-0 text-[#666666] transition-transform duration-200" />
|
||||
{children}
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
|
||||
@@ -6,6 +6,7 @@ import i18next from "i18next"
|
||||
import { useRef, useState } from "react"
|
||||
import { TipIcon } from "@/components/bs-icons/tip"
|
||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
interface ConfirmParams {
|
||||
title?: string
|
||||
@@ -77,7 +78,9 @@ function ConfirmWrapper() {
|
||||
el.id = 'confirm-wrap'
|
||||
document.body.append(el)
|
||||
}
|
||||
ReactDOM.render(<ConfirmWrapper />, el);
|
||||
// ReactDOM.render(<ConfirmWrapper />, el);
|
||||
const root = createRoot(el);
|
||||
root.render(<ConfirmWrapper />);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ const ButtonNumber = React.forwardRef<HTMLButtonElement, {
|
||||
setValue(updateValue)
|
||||
onChange?.(updateValue)
|
||||
}
|
||||
return (<div className={cname("flex items-center border input-border bg-gray-50 rounded-md", className)}>
|
||||
return (<div className={cname("flex items-center border input-border bg-[#1a1a1a] rounded-md", className)}>
|
||||
<Button variant="ghost" size={size} disabled={value === min} onClick={valueReduce}>-</Button>
|
||||
<span className="min-w-10 block text-center">{value}</span>
|
||||
<Button variant="ghost" size={size} disabled={value === max} onClick={valueAdd}>+</Button>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { cname } from "../utils"
|
||||
import { SearchIcon } from "../../bs-icons/search"
|
||||
import { generateUUID } from "../utils"
|
||||
import { MinusCircledIcon } from "@radix-ui/react-icons"
|
||||
import sousuo from "../../../assets/npc/sousuo1.png"
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> { }
|
||||
|
||||
@@ -12,7 +13,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cname(
|
||||
"flex h-9 w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-9 w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
@@ -27,8 +28,9 @@ Input.displayName = "Input"
|
||||
const SearchInput = React.forwardRef<HTMLInputElement, InputProps & { inputClassName?: string, iconClassName?: string }>(
|
||||
({ className, inputClassName, iconClassName, ...props }, ref) => {
|
||||
return <div className={cname("relative", className)}>
|
||||
<SearchIcon className={cname("h-5 w-5 absolute left-2 top-2", iconClassName)} />
|
||||
<Input type="text" ref={ref} className={cname("pl-8 bg-search-input", inputClassName)} {...props}></Input>
|
||||
{/* <SearchIcon className={cname("h-5 w-5 absolute left-2 top-2 text-[#666666]", iconClassName)} /> */}
|
||||
<img src={sousuo} alt="" className="w-[14px] absolute left-[14px] top-[10px]" />
|
||||
<Input type="text" ref={ref} className={cname("w-[244px] h-[34px] pl-[40px] npcInput3", inputClassName)} {...props}></Input>
|
||||
</div>
|
||||
}
|
||||
)
|
||||
|
||||
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"aspect-square h-4 w-4 rounded-full border-2 border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"aspect-square h-4 w-4 rounded-full border-2 border-[#FFD025] text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<span className="w-1.5 h-1.5 bg-primary rounded-full"></span>
|
||||
<span className="w-1.5 h-1.5 bg-[#FFD025] rounded-full"></span>
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
|
||||
@@ -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-[#fcfdff] 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 focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -124,7 +124,7 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-[#EBF0FF] focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-[#1a1a1a] focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { SearchInput } from "../input"
|
||||
const MultiItem = ({ active, children, value, onClick }) => {
|
||||
|
||||
return <div key={value}
|
||||
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 mb-1 text-sm outline-none hover:bg-[#EBF0FF] hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 break-all
|
||||
${active && 'bg-[#EBF0FF]'}`}
|
||||
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 mb-1 text-sm outline-none hover:bg-[#1a1a1a] hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 break-all
|
||||
${active && 'bg-[#1a1a1a]'}`}
|
||||
onClick={() => { onClick(value) }}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
@@ -93,7 +93,7 @@ const MultiSelect = ({
|
||||
setOptionFilter(newValues)
|
||||
}
|
||||
return <Select {...props} required onOpenChange={(e) => !e && setOptionFilter(options)}>
|
||||
<SelectTrigger className="mt-2 h-auto">
|
||||
<SelectTrigger className="mt-2 h-auto bg-[#1a1a1a]">
|
||||
{
|
||||
values.length
|
||||
? <div className="flex flex-wrap">
|
||||
|
||||
@@ -17,8 +17,8 @@ const Slider = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-[#997e1f]">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-[#ffd025]" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
|
||||
@@ -87,7 +87,7 @@ const TableCell = React.forwardRef<
|
||||
<td
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] bg-[#FBFBFB] first:rounded-l-md last:rounded-r-md group-odd:bg-[#f4f5f8] group-hover:bg-[#ebf0ff]",
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] bg-[#1a1a1a] first:rounded-l-md last:rounded-r-md group-odd:bg-[#1a1a1a] group-hover:bg-[#1a1a1a]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -32,10 +32,10 @@ const toastVariants = cva(
|
||||
error: "error border-[#D8341E] bg-[#FFF2F0] self-end",
|
||||
},
|
||||
message: {
|
||||
info: "shadow-xl bg-[#fff] self-center",
|
||||
success: "shadow-xl bg-[#fff] self-center",
|
||||
warning: "shadow-xl bg-[#fff] self-center",
|
||||
error: "shadow-xl bg-[#fff] self-center",
|
||||
info: "shadow-xl bg-[#333] self-center",
|
||||
success: "shadow-xl bg-[#333] self-center",
|
||||
warning: "shadow-xl bg-[#333] self-center",
|
||||
error: "shadow-xl bg-[#333] self-center",
|
||||
}
|
||||
},
|
||||
defaultVariants: {},
|
||||
@@ -99,7 +99,7 @@ const ToastTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cname("text-sm font-semibold [&+div]:text-xs group-[.info]:text-[#024FE5] group-[.success]:text-[#0BA95D] group-[.warning]:text-[#EA991F] group-[.error]:text-[#D8341E]", className)}
|
||||
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)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -111,7 +111,7 @@ const ToastDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cname("text-sm opacity-90", className)}
|
||||
className={cname("text-sm text-[#999]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -172,7 +172,9 @@ export async function deleteFlowFromDatabase(flowId: string) {
|
||||
export const createCustomFlowApi = async (params: {
|
||||
name: string,
|
||||
description: string,
|
||||
guide_word: string
|
||||
guide_word: string,
|
||||
avatar_img: string,
|
||||
avatar_color: string
|
||||
}, userName: string) => {
|
||||
const response: FlowType = await axios.post("/api/v1/flows/", {
|
||||
...params,
|
||||
@@ -224,7 +226,7 @@ export async function updataOnlineState(id, updatedFlow, open) {
|
||||
* @throws .
|
||||
*/
|
||||
export async function readOnlineFlows(page: number = 1, searchKey: string = "") {
|
||||
const { data, total }: { data: any, total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${100}&status=2&name=${searchKey}`);
|
||||
const data: { data: any, total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${100}&status=2&name=${searchKey}`);
|
||||
return data;
|
||||
}
|
||||
// export async function readOnlineFlows(page: number = 1) {
|
||||
|
||||
@@ -143,6 +143,13 @@ export async function uploadLibFile(data, config) {
|
||||
return await axios.post(`/api/v1/knowledge/upload`, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传能力头像
|
||||
*/
|
||||
export async function uploadNpcHeaderLibFile(data, config) {
|
||||
return await axios.post(`/api/v1/knowledge/upload_npc_header`, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定上传文件
|
||||
* file_path knowledge_id chunck_size
|
||||
@@ -168,14 +175,15 @@ export async function updateFileLib(data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改支持库
|
||||
*
|
||||
*/
|
||||
* 修改支持库
|
||||
*
|
||||
*/
|
||||
export async function getFileLibById(id) {
|
||||
return await axios.get(`/api/v1/knowledge/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 修改支持库文件
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uploadLibFile } from "../../controllers/API";
|
||||
import { uploadLibFile, uploadNpcHeaderLibFile } from "../../controllers/API";
|
||||
|
||||
// Function to upload the file with progress tracking
|
||||
export const uploadFileWithProgress = async (file, callback): Promise<any> => {
|
||||
@@ -29,3 +29,32 @@ export const uploadFileWithProgress = async (file, callback): Promise<any> => {
|
||||
// Handle errors
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadNpcHeaderLibFileWithProgress = async (file, callback): Promise<any> => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const config = {
|
||||
headers: { 'Content-Type': 'multipart/form-data;charset=utf-8' },
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const { loaded, total } = progressEvent;
|
||||
const progress = Math.round((loaded * 100) / total);
|
||||
console.log(`Upload progress: ${file.name} ${progress}%`);
|
||||
callback(progress)
|
||||
// You can update your UI with the progress information here
|
||||
},
|
||||
};
|
||||
|
||||
// Convert the FormData to binary using the FileReader API
|
||||
const data = await uploadNpcHeaderLibFile(formData, config);
|
||||
|
||||
console.log('Upload complete:', data);
|
||||
return data
|
||||
// Handle the response data as needed
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
return ''
|
||||
// Handle errors
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import ChatPanne from "./components/ChatPanne";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteChatApi, getChatsApi } from "../../controllers/API";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import { useDebounce } from "../../util/hook";
|
||||
import { useDebounce, useTable } from "../../util/hook";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import SkillTemps from "../SkillPage/components/SkillTemps";
|
||||
import duihuaDel from "../../assets/chat/duihua-del.png";
|
||||
@@ -59,10 +59,15 @@ export default function chatShare() {
|
||||
//
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const [onlineFlows, setOnlineFlows] = useState([])
|
||||
useEffect(() => {
|
||||
readOnlineFlows().then(setOnlineFlows)
|
||||
}, [])
|
||||
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(() => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { generateUUID } from "../../utils";
|
||||
import SkillTemps from "../SkillPage/components/SkillTemps";
|
||||
import ChatPanne from "./components/ChatPanne";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import { useDebounce } from "../../util/hook";
|
||||
import { useDebounce, useTable } from "../../util/hook";
|
||||
import duihuaDel from "../../assets/chat/duihua-del.png";
|
||||
import robot from "../../assets/robot.png";
|
||||
import robot2 from "../../assets/robot2.png";
|
||||
@@ -28,10 +28,15 @@ export default function SkillChatPage() {
|
||||
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const [onlineFlows, setOnlineFlows] = useState([])
|
||||
useEffect(() => {
|
||||
readOnlineFlows().then(setOnlineFlows)
|
||||
}, [])
|
||||
const {
|
||||
data: onlineFlows,
|
||||
loading,
|
||||
search,
|
||||
} = useTable<FlowType>({}, (param) =>
|
||||
readOnlineFlows(param.page, param.keyword).then((res) => {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
// 对话列表
|
||||
const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList()
|
||||
const chatIdRef = useRef('')
|
||||
|
||||
@@ -8,7 +8,7 @@ import ChatPanneM from "./ChatPanneM";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteChatApi, getChatsApi } from "../../../controllers/API";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../../controllers/request";
|
||||
import { useDebounce } from "../../../util/hook";
|
||||
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";
|
||||
@@ -55,10 +55,15 @@ export default function chatShare() {
|
||||
//
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const [onlineFlows, setOnlineFlows] = useState([])
|
||||
useEffect(() => {
|
||||
readOnlineFlows().then(setOnlineFlows)
|
||||
}, [])
|
||||
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(() => {
|
||||
|
||||
@@ -15,9 +15,9 @@ export default function CreateAssistant() {
|
||||
// State for form fields
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
roleAndTasks: `${t('build.example')}:
|
||||
${t('build.exampleOne')}
|
||||
${t('build.exampleTwo')}
|
||||
roleAndTasks: `示例:
|
||||
示例一
|
||||
示例二
|
||||
1. XX
|
||||
2. XX
|
||||
3. …`
|
||||
@@ -86,37 +86,45 @@ ${t('build.exampleTwo')}
|
||||
}
|
||||
};
|
||||
|
||||
return <DialogContent className="sm:max-w-[625px]">
|
||||
return <DialogContent className="sm:max-w-[625px] bg-[#000000]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('build.establishAssistant')}</DialogTitle>
|
||||
<DialogTitle className="text-[#fff]">创建NPC</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.giveAssistantName')} className="mt-2" value={formData.name} onChange={handleChange} />
|
||||
{errors.name && <p className="bisheng-tip mt-1">{errors.name}</p>}
|
||||
<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} />
|
||||
{errors.name && <p className="bisheng-tip mt-1 text-[#999999]">名称不可为空</p>}
|
||||
</div>
|
||||
<div className="">
|
||||
<label htmlFor="roleAndTasks" className="bisheng-label">{t('build.whatWant')}</label>
|
||||
<label htmlFor="roleAndTasks" className="bisheng-label text-[#999999]">你希望NPC的角色是什么,具体完成什么任务?</label>
|
||||
<Textarea
|
||||
id="roleAndTasks"
|
||||
name="roleAndTasks"
|
||||
placeholder={t('build.forExample')}
|
||||
placeholder=""
|
||||
maxLength={1000}
|
||||
className="mt-2 min-h-32"
|
||||
className="mt-2 min-h-32 npcInput overflow-auto scrollbar-hide"
|
||||
value={formData.roleAndTasks}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{errors.roleAndTasks && <p className="bisheng-tip mt-1">{errors.roleAndTasks}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
{/* <DialogFooter>
|
||||
<DialogClose>
|
||||
<Button variant="outline" className="px-11" type="button" onClick={() => setFormData({ name: '', roleAndTasks: '' })}>{t('cancle')}</Button>
|
||||
<Button variant="outline" className="px-11 baogao-btn baogao-btn2" type="button" onClick={() => setFormData({ name: '', roleAndTasks: '' })}>取 消</Button>
|
||||
</DialogClose>
|
||||
<Button disabled={loading} type="submit" className="px-11" onClick={handleSubmit}>
|
||||
<Button disabled={loading} type="submit" className="px-11 baogao-btn baogao-btn2" onClick={handleSubmit}>
|
||||
{loading && <LoadIcon className="mr-2" />}
|
||||
{t('build.create')}</Button>
|
||||
</DialogFooter>
|
||||
创 建</Button>
|
||||
</DialogFooter> */}
|
||||
<div className="flex justify-center ">
|
||||
<DialogClose>
|
||||
<Button variant="outline" className="px-11 baogao-btn baogao-btn2" type="button" onClick={() => setFormData({ name: '', roleAndTasks: '' })}>取 消</Button>
|
||||
</DialogClose>
|
||||
<Button disabled={loading} type="submit" className="px-11 baogao-btn baogao-btn2" onClick={handleSubmit}>
|
||||
{loading && <LoadIcon className="mr-2" />}
|
||||
创 建</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
};
|
||||
|
||||
@@ -47,14 +47,14 @@ export default function CreateTemp({ flow, open, setOpen, onCreated }: { flow: F
|
||||
return <dialog className={`modal bg-blur-shared ${open ? 'modal-open' : 'modal-close'}`} onClick={() => setOpen(false)}>
|
||||
<form method="dialog" className="max-w-[600px] flex flex-col modal-box bg-[#fff] shadow-lg dark:bg-background" onClick={e => e.stopPropagation()}>
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setOpen(false)}>✕</button>
|
||||
<h3 className="font-bold text-lg mb-4">{t('skills.createTemplate')}</h3>
|
||||
<h3 className="font-bold text-lg mb-4 text-[#fff]">{t('skills.createTemplate')}</h3>
|
||||
<div className="flex flex-wrap flex-col gap-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">NPC名称</Label>
|
||||
<Label htmlFor="name" className="text-right text-[#fff]">能力名称</Label>
|
||||
<Input id="name" value={data.name} onChange={(e) => setData({ ...data, name: e.target.value })} className="col-span-2" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">{t('skills.description')}</Label>
|
||||
<Label htmlFor="username" className="text-right text-[#fff]">{t('skills.description')}</Label>
|
||||
<Textarea id="name" value={data.description} onChange={(e) => setData({ ...data, description: e.target.value })} className="col-span-2" />
|
||||
</div>
|
||||
{/* <Button type="submit" className="h-8 w-[400px] rounded-full mt-6 mx-auto bg-[#FFD025]" onClick={handleSubmit}>{t('create')}</Button> */}
|
||||
|
||||
@@ -78,27 +78,27 @@ const TestDialog = forwardRef((props: any, ref) => {
|
||||
}
|
||||
|
||||
return <Dialog open={testShow} onOpenChange={setTestShow}>
|
||||
<DialogContent className="sm:max-w-[625px]">
|
||||
<DialogContent className="sm:max-w-[625px] bg-[#262626]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{apiData.name}</DialogTitle>
|
||||
<DialogTitle className="text-[#fff]">{apiData.name}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{testShow && <div className="flex flex-col gap-8 py-6">
|
||||
<div className="max-h-[600px] overflow-y-auto scrollbar-hide">
|
||||
<label htmlFor="name" className="bisheng-label">{t('test.parametersAndValues')}</label>
|
||||
<label htmlFor="name" className="bisheng-label text-[#fff]">{t('test.parametersAndValues')}</label>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">{t('test.parameter')}</TableHead>
|
||||
<TableHead >{t('test.value')}</TableHead>
|
||||
<TableHead className="w-[100px] text-[#fff]">{t('test.parameter')}</TableHead>
|
||||
<TableHead className="text-[#fff]">{t('test.value')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{
|
||||
apiData.api_params.map((param) =>
|
||||
<TableRow key={param.id}>
|
||||
<TableCell>{param.name}{param.required && <span className="text-red-500">*</span>}</TableCell>
|
||||
<TableCell className="text-[#fff]">{param.name}{param.required && <span className="text-red-500">*</span>}</TableCell>
|
||||
<TableCell>
|
||||
<Input onChange={(e) => {
|
||||
<Input className="npcInput1" onChange={(e) => {
|
||||
formRef.current[param.name] = e.target.value;
|
||||
}}></Input>
|
||||
</TableCell>
|
||||
@@ -113,10 +113,10 @@ const TestDialog = forwardRef((props: any, ref) => {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Button onClick={handleTest} disabled={loading}>{t('test.test')}</Button>
|
||||
<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">{t('test.result')}</label>
|
||||
<Textarea id="desc" name="desc" value={result} placeholder={t('test.outResultPlaceholder')} readOnly className="mt-2" />
|
||||
<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]" />
|
||||
</div>
|
||||
</div>}
|
||||
</DialogContent>
|
||||
@@ -305,33 +305,33 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
|
||||
return <div>
|
||||
<Sheet open={editShow} onOpenChange={setEditShow}>
|
||||
<SheetContent className="w-[800px] sm:max-w-[800px] p-4">
|
||||
<SheetContent className="w-[800px] bg-[#000] sm:max-w-[800px] p-4">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{delShow ? t('edit') : t('create')}{t('tools.createCustomTool')}</SheetTitle>
|
||||
<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">{t('tools.name')}</label>
|
||||
<label htmlFor="open" className="px-6 text-[#fff]">名称</label>
|
||||
<div className="px-6 mb-4" >
|
||||
<Input
|
||||
id="toolName"
|
||||
name="toolName"
|
||||
className="mt-2"
|
||||
placeholder={t('tools.enterToolName')}
|
||||
className="mt-2 npcInput2"
|
||||
placeholder="输入工具名称"
|
||||
value={formState.toolName}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{/* schema */}
|
||||
<div className="px-6 flex items-center justify-between">
|
||||
<label htmlFor="open">OpenAPI Schema</label>
|
||||
<label htmlFor="open" className="text-[#fff]">OpenAPI Schema</label>
|
||||
<div className="flex gap-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline"><PlusIcon /> {t('tools.importFromUrl')}</Button>
|
||||
<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">
|
||||
<div className="flex items-center gap-4 ">
|
||||
<Input
|
||||
id="schemaUrl"
|
||||
name="schemaUrl"
|
||||
@@ -339,19 +339,19 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
onChange={(e) => schemaUrl.current = e.target.value}
|
||||
/>
|
||||
<PopoverClose>
|
||||
<Button size="sm" className="w-16" onClick={handleImportSchema}>{t('skills.import')}</Button>
|
||||
<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('tools.examples')}</span>
|
||||
<span>{t('tools1.examples')}</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="json">{t('tools.weatherJson')}</SelectItem>
|
||||
<SelectItem value="yaml">{t('tools.petShopYaml')}</SelectItem>
|
||||
<SelectItem value="json">{t('tools1.weatherJson')}</SelectItem>
|
||||
<SelectItem value="yaml">{t('tools1.petShopYaml')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -361,17 +361,17 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
<Textarea
|
||||
id="schemaContent"
|
||||
name="schemaContent"
|
||||
placeholder={t('tools.enterOpenAPISchema')}
|
||||
className="mt-2 min-h-52"
|
||||
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">{t('tools.authenticationType')}</label>
|
||||
<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">{t('tools.authType')}</label>
|
||||
<label htmlFor="open" className="bisheng-label text-[#999]">{t('tools1.authType')}</label>
|
||||
<RadioGroup
|
||||
id="authMethod"
|
||||
name="authMethod"
|
||||
@@ -381,17 +381,17 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="none" id="r1" />
|
||||
<Label htmlFor="r1">{t('tools.none')}</Label>
|
||||
<Label htmlFor="r1" className="text-[#fff]">{t('tools1.none')}</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="apikey" id="r2" />
|
||||
<Label htmlFor="r2">{t('tools.apiKey')}</Label>
|
||||
<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" htmlFor="apiKey">API Key</label>
|
||||
<label className="bisheng-label text-[#fff]" htmlFor="apiKey">API Key</label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
@@ -402,7 +402,7 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
</div>
|
||||
|
||||
<div className="px-6 mb-4" >
|
||||
<label htmlFor="open" className="bisheng-label">Auth Type</label>
|
||||
<label htmlFor="open" className="bisheng-label text-[#fff]">Auth Type</label>
|
||||
<RadioGroup
|
||||
id="authType"
|
||||
name="authType"
|
||||
@@ -438,16 +438,16 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6">{t('tools.availableTools')}</label>
|
||||
<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]">{t('tools.name')}</TableHead>
|
||||
<TableHead >{t('tools.description')}</TableHead>
|
||||
<TableHead >{t('tools.method')}</TableHead>
|
||||
<TableHead >{t('tools.path')}</TableHead>
|
||||
<TableHead >{t('operations')}</TableHead>
|
||||
<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>
|
||||
@@ -468,17 +468,17 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
</TableRow>
|
||||
) :
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>{t('tools.none')}</TableCell>
|
||||
<TableCell colSpan={5}>{t('tools1.none')}</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="open" className="px-6">{t('tools.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">{t('tools.authType')}</label>
|
||||
<label htmlFor="open" className="bisheng-label">{t('tools1.authType')}</label>
|
||||
<RadioGroup
|
||||
id="authMethod"
|
||||
name="authMethod"
|
||||
@@ -488,17 +488,17 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="none" id="r1" />
|
||||
<Label htmlFor="r1">{t('tools.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">{t('tools.apiKey')}</Label>
|
||||
<Label htmlFor="r2">{t('tools1.apiKey')}</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{formState.authMethod === "apikey" && (
|
||||
<div className="px-6 mb-4">
|
||||
<label htmlFor="apiKey">{t('tools.apiKey')}</label>
|
||||
<label htmlFor="apiKey">{t('tools1.apiKey')}</label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
@@ -509,15 +509,15 @@ const EditTool = forwardRef((props: any, ref) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SheetFooter className="absolute bottom-0 right-0 w-full px-6 py-4 bg-[#fff]">
|
||||
<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('tools.delete')}</Button>}
|
||||
<Button size="sm" variant="outline" onClick={() => setEditShow(false)}>{t('tools.cancel')}</Button>
|
||||
<Button size="sm" onClick={handleSave}>{t('tools.save')}</Button>
|
||||
>{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 >
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Badge } from "@/components/bs-ui/badge";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/bs-ui/tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import gongjuIcon3 from "../../../assets/npc/gongjuIcon3.png"
|
||||
import gongjuBianji from "../../../assets/npc/gongjuBianji.png"
|
||||
|
||||
export default function ToolItem({
|
||||
type,
|
||||
@@ -20,20 +22,26 @@ export default function ToolItem({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <AccordionItem key={data.id} value={data.id} className="data-[state=open]:border-2 data-[state=open]:border-primary/20 data-[state=open]:rounded-md">
|
||||
return <AccordionItem key={data.id} value={data.id} className="data-[state=open]:border-[1px] data-[state=open]:border-[#FFD025]/40 data-[state=open]:rounded-md">
|
||||
<AccordionTrigger>
|
||||
<div className="group w-full flex gap-2 text-start relative pr-4">
|
||||
<TitleIconBg className="w-8 h-8 min-w-8" id={data.id} ><ToolIcon /></TitleIconBg>
|
||||
<TitleIconBg className="w-8 h-8 min-w-8" id={data.id} >
|
||||
<img src={gongjuIcon3} alt="" />
|
||||
</TitleIconBg>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="w-full text-sm font-medium leading-none flex items-center gap-2">{data.name}
|
||||
<div className="w-full text-sm font-medium leading-none flex items-center gap-2 text-[#FFFFFF]">{data.name}
|
||||
{
|
||||
type === 'edit' && <div
|
||||
className="group-hover:opacity-100 opacity-0 hover:bg-[#EAEDF3] rounded cursor-pointer"
|
||||
className="flex items-center justify-center w-[26px] h-[26px] bg-[#2A2A2A] group-hover:opacity-100 opacity-0 cursor-pointer"
|
||||
style={{borderRadius:"50px"}}
|
||||
onClick={(e) => onEdit(data.id)}
|
||||
><SettingIcon /></div>
|
||||
>
|
||||
{/* <SettingIcon /> */}
|
||||
<img src={gongjuBianji} className="w-[14px]" alt="" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-2">{data.description}</p>
|
||||
<p className="text-sm mt-2 text-[#666666]">{data.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
@@ -41,14 +49,14 @@ export default function ToolItem({
|
||||
<div className="px-6 mb-4">
|
||||
{data.children.map(api => (
|
||||
<div key={api.name} className="relative p-4 rounded-sm border-t">
|
||||
<h1 className="text-sm font-medium leading-none">{api.name}</h1>
|
||||
<p className="text-sm text-muted-foreground mt-2">{api.desc}</p>
|
||||
<h1 className="text-sm font-medium leading-none text-[#FFFFFF]">{api.name}</h1>
|
||||
<p className="text-sm mt-2 text-[#666666]">{api.desc}</p>
|
||||
{
|
||||
api.api_params.length > 0 && <p className="text-sm text-muted-foreground mt-2 flex gap-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-primary cursor-pointer">{t("build.params")}</span>
|
||||
<span className="cursor-pointer text-[#FFD025]">{t("build.params")}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="bg-gray-50 border shadow-md p-4 text-gray-950 max-w-[520px]">
|
||||
<p className="flex gap-2 items-center"><Badge>{JSON.parse(api.extra)?.method || 'http'}</Badge><span className="text-xl">{api.name}</span></p>
|
||||
@@ -72,7 +80,7 @@ export default function ToolItem({
|
||||
{
|
||||
api.api_params.map(param => (
|
||||
<div>
|
||||
<span className=" rounded-xl bg-gray-200 px-2 py-1 text-xs font-medium text-white">{param.name}</span>
|
||||
<span className=" rounded-xl bg-[#2E2406] px-2 py-1 text-xs font-medium text-[#FFFFFF]">{param.name}</span>
|
||||
{/* <span>{param.schema.type}</span> */}
|
||||
</div>
|
||||
))
|
||||
@@ -81,8 +89,8 @@ export default function ToolItem({
|
||||
}
|
||||
{
|
||||
select && (select.some(_ => _.id === api.id) ?
|
||||
<Button size="sm" className="absolute right-4 bottom-2 h-6" disabled>{t("build.added")}</Button>
|
||||
: <Button size="sm" className="absolute right-4 bottom-2 h-6" onClick={() => onSelect(api)}>{t("build.add")}</Button>)
|
||||
<Button size="sm" className="absolute right-4 bottom-2 h-6 bg-[#FFD025]" disabled>{t("build.added")}</Button>
|
||||
: <Button size="sm" className="absolute right-4 bottom-2 h-6 bg-[#FFD025]" onClick={() => onSelect(api)}>{t("build.add")}</Button>)
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import { LoadIcon } from "@/components/bs-icons/loading";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { DialogClose, DialogContent, DialogFooter } from "@/components/bs-ui/dialog";
|
||||
import { Textarea } from "@/components/bs-ui/input";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { AssistantTool } from "@/types/assistant";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const enum LoadType {
|
||||
Prompt = 5,
|
||||
GuideWord = 4,
|
||||
GuideQuestion = 3,
|
||||
Tool = 2,
|
||||
Flow = 1
|
||||
}
|
||||
|
||||
export default function AutoPromptDialog({ onOpenChange }) {
|
||||
const { toast } = useToast()
|
||||
const { id } = useParams()
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore()
|
||||
|
||||
const init = () => {
|
||||
const prompt = areaRef.current.value
|
||||
const apiUrl = `/api/v1/assistant/auto?assistant_id=${id}&prompt=${encodeURIComponent(prompt)}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
areaRef.current.value = ''
|
||||
let queue = LoadType.Prompt
|
||||
setLoading(queue)
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// console.log('parsedData :>> ', parsedData);
|
||||
switch (parsedData.type) {
|
||||
case 'prompt':
|
||||
areaRef.current.value += parsedData.message.replace('```markdown', ''); break
|
||||
case 'guide_word':
|
||||
guideAreaRef.current.value += parsedData.message; break
|
||||
case 'guide_question':
|
||||
setQuestion(parsedData.message); break
|
||||
case 'tool_list':
|
||||
setTools(parsedData.message); break
|
||||
case 'flow_list':
|
||||
setFlows(parsedData.message); break
|
||||
case 'end':
|
||||
setLoading(--queue)
|
||||
if (parsedData.message) {
|
||||
toast({
|
||||
title: t('tip'),
|
||||
variant: 'error',
|
||||
description: parsedData.message
|
||||
});
|
||||
}
|
||||
break
|
||||
}
|
||||
// 自动滚动
|
||||
areaRef.current.scrollTop = areaRef.current.scrollHeight;
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setLoading(0);
|
||||
toast({
|
||||
title: parsedData.error,
|
||||
variant: 'error',
|
||||
description: ''
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// api
|
||||
init()
|
||||
}, [])
|
||||
|
||||
const [loading, setLoading] = useState(0)
|
||||
const handleReload = () => {
|
||||
init()
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用
|
||||
*/
|
||||
const { message } = useToast()
|
||||
// state
|
||||
const areaRef = useRef(null)
|
||||
const guideAreaRef = useRef(null)
|
||||
const [question, setQuestion] = useState<string[]>([])
|
||||
const [tools, setTools] = useState<AssistantTool[]>([])
|
||||
const [flows, setFlows] = useState<FlowType[]>([])
|
||||
// 更新提示词
|
||||
const handleUsePropmt = () => {
|
||||
const value = areaRef.current.value
|
||||
dispatchAssistant('setPrompt', { prompt: value })
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.promptReplaced')
|
||||
})
|
||||
}
|
||||
|
||||
const handleUserQuestion = () => {
|
||||
dispatchAssistant('setQuestion', { guide_question: [...question, ''] })
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.guideReplaced')
|
||||
})
|
||||
}
|
||||
|
||||
const handleUseGuide = () => {
|
||||
const value = guideAreaRef.current.value
|
||||
dispatchAssistant('setGuideword', { guide_word: value })
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.openingReplaced')
|
||||
})
|
||||
}
|
||||
|
||||
const handleUseTools = () => {
|
||||
dispatchAssistant('setTools', { tool_list: tools })
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.toolsReplaced')
|
||||
})
|
||||
}
|
||||
|
||||
const handleUseFlows = () => {
|
||||
dispatchAssistant('setFlows', { flow_list: flows })
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.skillsReplaced')
|
||||
})
|
||||
}
|
||||
|
||||
const handleUseAll = () => {
|
||||
dispatchAssistant('setPrompt', { prompt: areaRef.current.value })
|
||||
dispatchAssistant('setGuideword', { guide_word: guideAreaRef.current.value })
|
||||
dispatchAssistant('setTools', { tool_list: tools })
|
||||
dispatchAssistant('setFlows', { flow_list: flows })
|
||||
dispatchAssistant('setQuestion', { guide_question: [...question, ''] })
|
||||
// 收集结果
|
||||
message({
|
||||
variant: 'success',
|
||||
title: t('tip'),
|
||||
description: t('build.allReplaced')
|
||||
})
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
|
||||
return <DialogContent className="sm:max-w-[925px] bg-[#262626]">
|
||||
<div className="flex">
|
||||
{/* 提示词 */}
|
||||
<div className="w-[50%] relative pr-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg font-semibold leading-none tracking-tight flex text-[#fff]">NPC画像优化{LoadType.Prompt === loading && <LoadIcon className="ml-2 text-gray-600" />}</span>
|
||||
<Button variant="link" size="sm" onClick={handleReload} disabled={!!loading} className="text-[#b39012]" ><ReloadIcon className="mr-2" />重试</Button>
|
||||
</div>
|
||||
<div className="group flex justify-end mt-2 h-[600px] relative">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
{/* 自动配置 */}
|
||||
<div className="w-[50%] border-l pl-6">
|
||||
<div>
|
||||
<span className="text-lg font-semibold leading-none tracking-tight text-[#fff]">{t('build.automaticallyConfigurations')}</span>
|
||||
</div>
|
||||
<div className="max-h-[660px] overflow-y-auto">
|
||||
{/* 开场白 */}
|
||||
<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>
|
||||
<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>
|
||||
{/* 引导词 */}
|
||||
<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.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>
|
||||
))
|
||||
}
|
||||
<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>
|
||||
</div>
|
||||
{/* 工具 */}
|
||||
<div className="group relative pb-10 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.tools')}{LoadType.Tool === loading && <LoadIcon className="ml-2 text-gray-600" />}</div>
|
||||
<div className="pt-1">
|
||||
{
|
||||
tools.map(tool => (
|
||||
<div key={tool.id} className="flex gap-2 items-center mt-2">
|
||||
<TitleIconBg id={tool.id} className=" w-7 h-7" />
|
||||
<p className="text-sm">{tool.name}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
className="group-hover:flex hidden h-6 absolute bottom-4 right-4 baogao-btn2"
|
||||
disabled={LoadType.Tool <= loading || !tools.length} size="sm"
|
||||
onClick={handleUseTools}
|
||||
>{t('build.use')}</Button>
|
||||
</div>
|
||||
{/* 技能 */}
|
||||
<div className="group relative pb-10 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.skill')}{LoadType.Flow === loading && <LoadIcon className="ml-2 text-gray-600" />}</div>
|
||||
<div className="pt-1">
|
||||
{
|
||||
flows.map(flow => (
|
||||
<div key={flow.id} className="flex gap-2 items-center mt-2">
|
||||
<TitleIconBg id={flow.id} className=" w-7 h-7" />
|
||||
<p className="text-sm">{flow.name}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
className="group-hover:flex hidden h-6 absolute bottom-4 right-4 baogao-btn2"
|
||||
disabled={LoadType.Flow <= loading || !flows.length}
|
||||
size="sm"
|
||||
onClick={handleUseFlows}
|
||||
>{t('build.use')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button variant="outline" className="px-11 baogao-btn baogao-btn2" type="button">{t('cancle')}</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="px-11 baogao-btn baogao-btn2" disabled={!!loading} onClick={handleUseAll}>{t('build.useAll')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
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";
|
||||
|
||||
export default function EditAssistantDialog({ name, desc, onSave }) {
|
||||
|
||||
const { t } = useTranslation()
|
||||
// State for form fields
|
||||
const [formData, setFormData] = useState({ name: '', desc: '' });
|
||||
|
||||
useEffect(() => {
|
||||
setFormData({ name, desc })
|
||||
}, [name, desc])
|
||||
// console.log(formData, name, desc);
|
||||
|
||||
// State for errors
|
||||
const [errors, setErrors] = useState<any>({});
|
||||
|
||||
// Validate form fields
|
||||
const validateField = (name, value) => {
|
||||
switch (name) {
|
||||
case 'name':
|
||||
if (!value) return t('build.nameRequired');
|
||||
if (value.length > 50) return t('build.nameMaxLength');
|
||||
return '';
|
||||
case 'desc':
|
||||
if (value.length > 1000) return t('build.descMaxLength');
|
||||
return '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Handle field change
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
const error = validateField(name, value);
|
||||
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
setErrors(prev => ({ ...prev, [name]: error }));
|
||||
};
|
||||
|
||||
// Validate entire form
|
||||
const validateForm = () => {
|
||||
const formErrors = {};
|
||||
let isValid = true;
|
||||
|
||||
Object.keys(formData).forEach(key => {
|
||||
const error = validateField(key, formData[key]);
|
||||
if (error) {
|
||||
formErrors[key] = error;
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
setErrors(formErrors);
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const { message, toast } = useToast()
|
||||
// Handle form submission
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const isValid = validateForm();
|
||||
// console.log('Form data:', errors);
|
||||
if (!isValid) return toast({
|
||||
title: t('prompt'),
|
||||
variant: 'error',
|
||||
description: Object.keys(errors).map(key => errors[key]),
|
||||
})
|
||||
|
||||
onSave(formData)
|
||||
|
||||
};
|
||||
|
||||
return <DialogContent className="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{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>
|
||||
<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} />
|
||||
{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>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="px-11" onClick={handleSubmit}>{t('build.confirm')}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
};
|
||||
|
||||
58
src/pages/SkillPage/components/editAssistant/Header.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import { AssistantIcon } from "@/components/bs-icons/assistant";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Dialog, DialogTrigger } from "@/components/bs-ui/dialog";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { ChevronLeftIcon, Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import EditAssistantDialog from "./EditAssistantDialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Header({ onSave, onLine }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore()
|
||||
{/* 编辑助手 */ }
|
||||
const [editShow, setEditShow] = useState(false);
|
||||
|
||||
const needSaveRef = useRef(false)
|
||||
useEffect(() => {
|
||||
if (needSaveRef.current) {
|
||||
needSaveRef.current = false
|
||||
onSave()
|
||||
}
|
||||
}, [assistantState])
|
||||
const handleEditSave = (form) => {
|
||||
dispatchAssistant('setBaseInfo', form)
|
||||
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>
|
||||
<span className="bisheng-title text-[#fff]">{assistantState.name}</span>
|
||||
{/* edit dialog */}
|
||||
<Dialog open={editShow} onOpenChange={setEditShow}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="text-[#fff]"><Pencil2Icon /></Button>
|
||||
</DialogTrigger>
|
||||
{
|
||||
editShow && <EditAssistantDialog
|
||||
name={assistantState.name}
|
||||
desc={assistantState.desc}
|
||||
onSave={handleEditSave}></EditAssistantDialog>
|
||||
}
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button variant="outline" className="baogao-btn baogao-btn2" type="button" onClick={onSave}>{t('build.save')}</Button>
|
||||
<Button type="submit" className="baogao-btn baogao-btn2 ml-[27px]" onClick={onLine}>{t('build.online')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import MultiSelect from "@/components/bs-ui/select/multi";
|
||||
import { readFileLibDatabase } from "@/controllers/API";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function KnowledgeBaseMulti({ value, onChange, children }:
|
||||
{ value: any, onChange: (a: any) => any, children: (fun: any) => React.ReactNode }) {
|
||||
|
||||
const { t } = useTranslation()
|
||||
const [options, setOptions] = useState<any>([]);
|
||||
const originOptionsRef = useRef([])
|
||||
const reload = () => {
|
||||
readFileLibDatabase(1, 400).then(res => {
|
||||
originOptionsRef.current = res.data
|
||||
setOptions(res.data.map(el => ({ label: el.name, value: el.id })))
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, [])
|
||||
|
||||
const handleChange = (res) => {
|
||||
// id => obj
|
||||
onChange(res.map(el => originOptionsRef.current.find(el2 => el2.id === el)))
|
||||
}
|
||||
|
||||
return <MultiSelect
|
||||
value={value.map(el => el.id)}
|
||||
options={options}
|
||||
placeholder={t('build.selectKnowledgeBase')}
|
||||
searchPlaceholder={t('build.searchBaseName')}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{children(reload)}
|
||||
</MultiSelect>
|
||||
};
|
||||
29
src/pages/SkillPage/components/editAssistant/ModelSelect.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select";
|
||||
import { getAssistantModelsApi } from "@/controllers/API/assistant";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ModelSelect({ value, onChange }) {
|
||||
|
||||
const [configServers, setConfigServers] = useState([])
|
||||
const loadModels = async () => {
|
||||
const data = await getAssistantModelsApi()
|
||||
setConfigServers(data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadModels()
|
||||
}, [])
|
||||
|
||||
return <Select name="model" required value={value} onValueChange={onChange}>
|
||||
<SelectTrigger className="mt-2 text-[#fff] bg-[#1a1a1a]">
|
||||
<SelectValue placeholder="选择一个模型" ></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{
|
||||
configServers.map(server => <SelectItem key={server.id} value={server.model_name}>{server.model_name}</SelectItem>)
|
||||
}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
};
|
||||
45
src/pages/SkillPage/components/editAssistant/Prompt.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Dialog, DialogTrigger } from "@/components/bs-ui/dialog";
|
||||
import { MixerHorizontalIcon } from "@radix-ui/react-icons";
|
||||
import AutoPromptDialog from "./AutoPromptDialog";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { Textarea } from "@/components/bs-ui/input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Prompt() {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore()
|
||||
|
||||
useEffect(() => {
|
||||
// 新建助手自动开启优化
|
||||
if (window.assistantCreate && assistantState.prompt) {
|
||||
setOpen(true)
|
||||
delete window.assistantCreate
|
||||
}
|
||||
}, [assistantState.prompt])
|
||||
|
||||
return <div className="w-[50%] h-full bg-[#1a1a1a] shadow-sm p-4 overflow-y-auto scrollbar-hide">
|
||||
<div className="flex-between-center relative">
|
||||
<span className="text-sm font-medium leading-none text-[#fff]">{t('build.assistantPortrait')}</span>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="link" className="p-0 absolute right-[10px] text-[#b39012]"><MixerHorizontalIcon className="mr-1" />{t('build.automaticOptimization')}</Button>
|
||||
</DialogTrigger>
|
||||
{open && <AutoPromptDialog onOpenChange={setOpen}></AutoPromptDialog>}
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="h-[90%]">
|
||||
<Textarea
|
||||
className=" scrollbar-hide h-full focus-visible:ring-0 resize-none npcInput2 mt-[10px]"
|
||||
value={assistantState.prompt}
|
||||
placeholder={t('build.prompt')}
|
||||
onInput={(e => dispatchAssistant('setPrompt', { prompt: e.target.value }))}
|
||||
></Textarea>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
302
src/pages/SkillPage/components/editAssistant/Setting.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import SkillSheet from "@/components/bs-comp/sheets/SkillSheet";
|
||||
import ToolsSheet from "@/components/bs-comp/sheets/ToolsSheet";
|
||||
import { ToolIcon } from "@/components/bs-icons/tool";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/bs-ui/accordion";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { InputList, Textarea } from "@/components/bs-ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/bs-ui/tooltip";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import {
|
||||
MinusCircledIcon,
|
||||
PlusCircledIcon,
|
||||
PlusIcon,
|
||||
QuestionMarkCircledIcon,
|
||||
ReloadIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import KnowledgeBaseMulti from "./KnowledgeBaseMulti";
|
||||
import ModelSelect from "./ModelSelect";
|
||||
import Temperature from "./Temperature";
|
||||
|
||||
export default function Setting() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
id="skill-scroll"
|
||||
className="h-full w-[50%] overflow-y-auto scrollbar-hide"
|
||||
>
|
||||
<h1 className="border bg-[#1a1a1a] indent-4 text-sm text-[#999] leading-8">
|
||||
{t("build.basicConfiguration")}
|
||||
</h1>
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{/* 基础配置 */}
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-[#fff]">
|
||||
<span>{t("build.modelConfiguration")}</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="py-2">
|
||||
<div className="mb-4 px-6">
|
||||
<label htmlFor="model" className="bisheng-label text-[#999]">
|
||||
{t("build.model")}
|
||||
</label>
|
||||
<ModelSelect
|
||||
value={assistantState.model_name}
|
||||
onChange={(val) =>
|
||||
dispatchAssistant("setting", { model_name: val })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 px-6">
|
||||
<label htmlFor="slider" className="bisheng-label text-[#999]">
|
||||
{t("build.temperature")}
|
||||
</label>
|
||||
<Temperature
|
||||
value={assistantState.temperature}
|
||||
onChange={(val) =>
|
||||
dispatchAssistant("setting", { temperature: val })
|
||||
}
|
||||
></Temperature>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* 开场引导 */}
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger className="text-[#fff]">
|
||||
<span>{t("build.openingIntroduction")}</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="py-2">
|
||||
<div className="mb-4 px-6">
|
||||
<label htmlFor="open" className="bisheng-label text-[#999]">
|
||||
{t("build.openingStatement")}
|
||||
</label>
|
||||
<Textarea
|
||||
name="open"
|
||||
className="mt-2 min-h-[34px] npcInput2"
|
||||
style={{ height: 56 }}
|
||||
placeholder={t("build.assistantMessageFormat")}
|
||||
value={assistantState.guide_word}
|
||||
onChange={(e) =>
|
||||
dispatchAssistant("setting", { guide_word: e.target.value })
|
||||
}
|
||||
></Textarea>
|
||||
{assistantState.guide_word.length > 1000 && (
|
||||
<p className="bisheng-tip mt-1">
|
||||
{t("build.maximumPromptLength")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4 px-6">
|
||||
<label htmlFor="open" className="bisheng-label flex gap-1 text-[#999]">
|
||||
{t("build.guidingQuestions")}
|
||||
<TooltipProvider delayDuration={200}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<QuestionMarkCircledIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("build.recommendQuestionsForUsers")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</label>
|
||||
<InputList
|
||||
rules={[{ maxLength: 50, message: t("build.maxCharacters50") }]}
|
||||
value={assistantState.guide_question}
|
||||
onChange={(list) => {
|
||||
dispatchAssistant("setting", { guide_question: list });
|
||||
}}
|
||||
placeholder={t("build.enterGuidingQuestions")}
|
||||
></InputList>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<h1 className="border-b bg-[#1a1a1a] text-[#999] indent-4 text-sm leading-8">
|
||||
{t("build.knowledge")}
|
||||
</h1>
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{/* 知识库 */}
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-[#fff]">
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<span>{t("build.knowledgeBase")}</span>
|
||||
{/* <Popover>
|
||||
<PopoverTrigger asChild className="group">
|
||||
<Button variant="link" size="sm"><TriangleRightIcon className="group-data-[state=open]:rotate-90" /> {t('build.autoCall')}</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[560px]">
|
||||
<div className="flex justify-between">
|
||||
<label htmlFor="model" className="bisheng-label">{t('build.callingMethod')}</label>
|
||||
<div>
|
||||
<RadioCard checked={false} title={t('build.autoCall')} description={t('build.autoCallDescription')} calssName="mb-4"></RadioCard>
|
||||
<RadioCard checked title={t('build.onDemandCall')} description={t('build.onDemandCallDescription')} calssName="mt-4"></RadioCard>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover> */}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="py-2">
|
||||
<div className="mb-4 px-6">
|
||||
<div className="flex gap-4">
|
||||
<KnowledgeBaseMulti
|
||||
value={assistantState.knowledge_list}
|
||||
onChange={(vals) =>
|
||||
dispatchAssistant("setting", { knowledge_list: vals })
|
||||
}
|
||||
>
|
||||
{(reload) => (
|
||||
<div className="flex justify-between">
|
||||
<Link to={"/filelib"} target="_blank">
|
||||
<Button variant="link">
|
||||
<PlusCircledIcon className="mr-1" />{" "}
|
||||
{t("build.createNewKnowledge")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="link" onClick={reload}>
|
||||
<ReloadIcon className="mr-1" /> {t("build.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</KnowledgeBaseMulti>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<h1 className="border-b bg-[#1a1a1a] text-[#999] indent-4 text-sm leading-8">
|
||||
{t("build.abilities")}
|
||||
</h1>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="w-full"
|
||||
onValueChange={(e) =>
|
||||
e.includes("skill") &&
|
||||
document.getElementById("skill-scroll").scrollTo({ top: 9999 })
|
||||
}
|
||||
>
|
||||
{/* 工具 */}
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-[#fff]">
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<span>{t("build.tools")}</span>
|
||||
<ToolsSheet
|
||||
select={assistantState.tool_list}
|
||||
onSelect={(tool) =>
|
||||
dispatchAssistant("setting", {
|
||||
tool_list: [...assistantState.tool_list, tool],
|
||||
})
|
||||
}
|
||||
>
|
||||
<PlusIcon
|
||||
className="mr-2 text-[#ffd025]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></PlusIcon>
|
||||
</ToolsSheet>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="px-4">
|
||||
{assistantState.tool_list.map((tool) => (
|
||||
<div
|
||||
key={tool.id}
|
||||
className="group mt-2 flex cursor-pointer items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TitleIconBg id={tool.id} className="h-7 w-7">
|
||||
<ToolIcon />
|
||||
</TitleIconBg>
|
||||
<p className="text-sm">{tool.name}</p>
|
||||
</div>
|
||||
<MinusCircledIcon
|
||||
className="hidden text-primary group-hover:block"
|
||||
onClick={() =>
|
||||
dispatchAssistant("setting", {
|
||||
tool_list: assistantState.tool_list.filter(
|
||||
(t) => t.id !== tool.id
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* 技能 */}
|
||||
<AccordionItem value="skill">
|
||||
<AccordionTrigger className="text-[#fff]">
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<span className="flex items-center gap-1">
|
||||
<span>能力</span>
|
||||
<TooltipProvider delayDuration={200}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<QuestionMarkCircledIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("build.skillDescription")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
<SkillSheet
|
||||
select={assistantState.flow_list}
|
||||
onSelect={(flow) =>
|
||||
dispatchAssistant("setting", {
|
||||
flow_list: [...assistantState.flow_list, flow],
|
||||
})
|
||||
}
|
||||
>
|
||||
<PlusIcon
|
||||
className="mr-2 text-[#ffd025]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></PlusIcon>
|
||||
</SkillSheet>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="px-4">
|
||||
{assistantState.flow_list.map((flow) => (
|
||||
<div
|
||||
key={flow.id}
|
||||
className="group mt-2 flex cursor-pointer items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TitleIconBg id={flow.id} className="h-7 w-7"></TitleIconBg>
|
||||
<p className="text-sm">{flow.name}</p>
|
||||
</div>
|
||||
<MinusCircledIcon
|
||||
className="hidden text-primary group-hover:block"
|
||||
onClick={() =>
|
||||
dispatchAssistant("setting", {
|
||||
flow_list: assistantState.flow_list.filter(
|
||||
(t) => t.id !== flow.id
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/pages/SkillPage/components/editAssistant/Temperature.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ButtonNumber } from "@/components/bs-ui/button";
|
||||
import { Slider } from "@/components/bs-ui/slider";
|
||||
|
||||
export default function Temperature({ value, onChange }) {
|
||||
|
||||
const props = { max: 2, min: 0, step: 0.1 }
|
||||
|
||||
return <div className="flex gap-4 mt-2">
|
||||
<Slider
|
||||
name="slider"
|
||||
value={[value]}
|
||||
onValueChange={(v) => onChange(v[0])}
|
||||
{...props}
|
||||
/>
|
||||
<ButtonNumber
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
};
|
||||
55
src/pages/SkillPage/components/editAssistant/TestChat.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import ChatComponent from "@/components/bs-comp/chatComponent";
|
||||
import { useMessageStore } from "@/components/bs-comp/chatComponent/messageStore";
|
||||
import { AssistantIcon } from "@/components/bs-icons/assistant";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TestChat({ assisId, guideQuestion }) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
const wsUrl = `${location.host}/api/v1/assistant/chat/${assisId}?t=${token}`
|
||||
|
||||
const { messages, changeChatId } = useMessageStore()
|
||||
const { assistantState } = useAssistantStore()
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 编辑页生成唯一id
|
||||
// const chatIdRef = useRef(generateUUID(32))
|
||||
useEffect(() => {
|
||||
// 建立 websocket
|
||||
changeChatId('')
|
||||
}, [])
|
||||
|
||||
// send 前获取参数用来做 params to send ws
|
||||
const getWsParamData = (action, msg, data) => {
|
||||
const inputKey = 'input';
|
||||
const msgData = {
|
||||
chatHistory: messages,
|
||||
flow_id: '',
|
||||
chat_id: '',
|
||||
name: assistantState.name,
|
||||
description: assistantState.desc,
|
||||
inputs: {}
|
||||
} as any
|
||||
if (msg) msgData.inputs = { [inputKey]: msg }
|
||||
if (data) msgData.inputs.data = data
|
||||
if (action === 'continue') msgData.action = action
|
||||
return [msgData, inputKey]
|
||||
}
|
||||
|
||||
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>
|
||||
<span className="text-sm text-[#fff]">{t('build.debugPreview')}</span>
|
||||
</div>
|
||||
<ChatComponent
|
||||
clear
|
||||
questions={guideQuestion}
|
||||
useName=''
|
||||
guideWord=''
|
||||
wsUrl={wsUrl}
|
||||
onBeforSend={getWsParamData}
|
||||
></ChatComponent>
|
||||
</div>
|
||||
};
|
||||
127
src/pages/SkillPage/editAssistant.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useMessageStore } from "@/components/bs-comp/chatComponent/messageStore";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { changeAssistantStatusApi, saveAssistanttApi } from "@/controllers/API/assistant";
|
||||
import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Header from "./components/editAssistant/Header";
|
||||
import Prompt from "./components/editAssistant/Prompt";
|
||||
import Setting from "./components/editAssistant/Setting";
|
||||
import TestChat from "./components/editAssistant/TestChat";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function editAssistant() {
|
||||
const { t } = useTranslation()
|
||||
const { id: assisId } = useParams()
|
||||
const navigate = useNavigate()
|
||||
// assistant data
|
||||
const { assistantState, changed, loadAssistantState, saveAfter, destroy } = useAssistantStore()
|
||||
const { startNewRound, insetSystemMsg, insetBsMsg, setShowGuideQuestion } = useMessageStore()
|
||||
|
||||
useEffect(() => {
|
||||
loadAssistantState(assisId).then((res) => {
|
||||
setShowGuideQuestion(true)
|
||||
setGuideQuestion(res.guide_question?.filter((item) => item) || [])
|
||||
res.guide_word && insetBsMsg(res.guide_word)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 展示的引导词独立存储
|
||||
const [guideQuestion, setGuideQuestion] = useState([])
|
||||
const handleStartChat = async (params) => {
|
||||
if (!handleCheck()) return
|
||||
await handleSave(true)
|
||||
saveAfter()
|
||||
startNewRound()
|
||||
setGuideQuestion(assistantState.guide_question.filter((item) => item))
|
||||
assistantState.guide_word && insetBsMsg(assistantState.guide_word)
|
||||
}
|
||||
|
||||
const { message, toast } = useToast()
|
||||
// 保存助手详细信息
|
||||
const handleSave = async (showMessage = false) => {
|
||||
if (!handleCheck()) return
|
||||
await captureAndAlertRequestErrorHoc(saveAssistanttApi({
|
||||
...assistantState,
|
||||
flow_list: assistantState.flow_list.map(item => item.id),
|
||||
tool_list: assistantState.tool_list.map(item => item.id),
|
||||
knowledge_list: assistantState.knowledge_list.map(item => item.id),
|
||||
guide_question: assistantState.guide_question.filter((item) => item)
|
||||
})).then(res => {
|
||||
if (!res) return
|
||||
showMessage && message({
|
||||
title: t('prompt'),
|
||||
variant: 'success',
|
||||
description: t('skills.saveSuccessful')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 上线助手
|
||||
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')
|
||||
// })
|
||||
// })
|
||||
// setTimeout(() => {
|
||||
// navigate('/build')
|
||||
// }, 1200);
|
||||
}
|
||||
|
||||
// 校验助手数据
|
||||
const handleCheck = () => {
|
||||
const errors = []
|
||||
if (assistantState.guide_question.some(que => que.length > 50)) {
|
||||
errors.push(t('skills.guideQuestions50'))
|
||||
}
|
||||
if (assistantState.guide_word.length > 1000) {
|
||||
errors.push(t('skills.promptWords1000'))
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'error',
|
||||
description: errors
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 销毁
|
||||
useEffect(() => {
|
||||
return destroy
|
||||
}, [])
|
||||
|
||||
return <div className="bg-[#262626]">
|
||||
<Header onSave={() => handleSave(true)} onLine={handleOnline}></Header>
|
||||
<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)]">
|
||||
<Prompt></Prompt>
|
||||
<Setting></Setting>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[40%] h-full bg-[#000000] relative">
|
||||
<TestChat guideQuestion={guideQuestion} assisId={assisId}></TestChat>
|
||||
{/* 变更触发保存的蒙版按钮 */}
|
||||
{changed && <div className="absolute w-full bottom-0 h-60" onClick={handleStartChat}></div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
@@ -18,7 +18,10 @@ import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import shangchuan from "../../assets/npc/shangchuan.png";
|
||||
import shou from "../../assets/npc/shou.png";
|
||||
import huifumoren from "../../assets/npc/huifumoren.png";
|
||||
import { uploadFileWithProgress } from "../../modals/UploadModal/upload";
|
||||
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";
|
||||
|
||||
export default function l2Edit() {
|
||||
const { t } = useTranslation()
|
||||
@@ -36,6 +39,7 @@ export default function l2Edit() {
|
||||
const descRef = useRef(null)
|
||||
const guideRef = useRef(null)
|
||||
const [logo, setLogo] = useState("")
|
||||
const randomNum = Math.floor(Math.random()*(4-0+1)+0);
|
||||
|
||||
useEffect(() => {
|
||||
// 无id不再请求
|
||||
@@ -82,20 +86,23 @@ export default function l2Edit() {
|
||||
return !!errorlist.length;
|
||||
}
|
||||
|
||||
|
||||
const navigate = useNavigate()
|
||||
// 创建新技能
|
||||
const handleCreateNewSkill = async () => {
|
||||
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,
|
||||
guide_word: guideWords
|
||||
guide_word: guideWords,
|
||||
avatar_img,
|
||||
avatar_color
|
||||
}, user.user_name).then(newFlow => {
|
||||
setFlow('l2 create flow', newFlow)
|
||||
navigate("/flow/" + newFlow.id, { replace: true }); // l3
|
||||
@@ -126,12 +133,14 @@ 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
|
||||
setLoading(true)
|
||||
formRef.current?.save()
|
||||
console.log(flow,name,description,guideWords,logo)
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords, logo}, true)
|
||||
await saveFlow({...flow, name, description, guide_word: guideWords, avatar_img, avatar_color}, true)
|
||||
setLoading(false)
|
||||
setSuccessData({ title: t('success') });
|
||||
setTimeout(() => /^\/skill\/[\w\d-]+/.test(location.pathname) && navigate(-1), 2000);
|
||||
@@ -167,7 +176,7 @@ export default function l2Edit() {
|
||||
// Check if the file type is correct
|
||||
// if (file && checkFileType(file.name)) {
|
||||
// Upload the file
|
||||
uploadFileWithProgress(file, (progress) => { }).then(res => {
|
||||
uploadNpcHeaderLibFileWithProgress(file, (progress) => { }).then(res => {
|
||||
// isSSO ? uploadFileWithProgress(file, (progress) => { }).then(res => {
|
||||
setLoading(false);
|
||||
if (typeof res === 'string') return setErrorData({ title: "Error", list: [res] })
|
||||
@@ -235,18 +244,18 @@ export default function l2Edit() {
|
||||
</div>
|
||||
<div className="skillSettingsDiv">
|
||||
<div className="pt-[20px] pr-[14px] pl-[14px]">
|
||||
<p>NPC头像</p>
|
||||
<p>能力头像</p>
|
||||
<div className="flex items-center ml-[7px] mt-[10px]">
|
||||
{!logo ? <img src="http://npcall.ai:8402/tmp-dir/robot.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240523%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240523T074305Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=33c17721784906036e8c55c1a2ed2ccdb8ead8311d3467588d6fdc92e3b2a88a" className="w-[34px] h-[34px]" onClick={handleButtonClick} alt="" /> : <img src={logo} className="w-[34px] h-[34px]" onClick={handleButtonClick} alt="" />}
|
||||
{!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-[27px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}}>
|
||||
<div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setLogo(robot)}>
|
||||
<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="pt-[20px] pr-[14px] pl-[14px]">
|
||||
<p>NPC名称</p>
|
||||
<p>能力名称</p>
|
||||
<Input ref={nameRef} placeholder={t('skills.skillName')} className={`mt-2 ${error.name && 'border-red-400'}`} />
|
||||
</div>
|
||||
<div className="pt-[20px] pr-[14px] pl-[14px]">
|
||||
|
||||
@@ -12,19 +12,46 @@ import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
|
||||
import DialogForceUpdate from "@/components/bs-ui/dialog/DialogForceUpdate";
|
||||
import robot from "../../assets/robot.png";
|
||||
import robot2 from "../../assets/robot2.png";
|
||||
import robot3 from "../../assets/robot3.png";
|
||||
import bianji from "../../assets/npc/bianji.png";
|
||||
import create from "../../assets/npc/create.png";
|
||||
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 zidingyijia from "../../assets/npc/zidingyijia.png";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { updataOnlineState } from "@/controllers/API/flow";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { userContext } from "@/contexts/userContext";
|
||||
import ShareLink from "./externalUse/shareLink";
|
||||
|
||||
export default function Assistants() {
|
||||
const { t } = useTranslation()
|
||||
const { user } = useContext(userContext);
|
||||
const navigate = useNavigate()
|
||||
const { message } = useToast()
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isShareLink, setIsShareLink] = useState(false)
|
||||
const [isShareLinkData, setIsShareLinkData] = useState("Any");
|
||||
const { page, pageSize, data: dataSource, total, loading, setPage, search, reload, refreshData } = useTable<AssistantItemDB>({ pageSize: 15 }, (param) =>
|
||||
getAssistantsApi(param.page, param.pageSize, param.keyword)
|
||||
)
|
||||
|
||||
if(dataSource[0] && dataSource[0].type != 0){
|
||||
dataSource.unshift({"type":0})
|
||||
}
|
||||
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const handleDelete = (data) => {
|
||||
bsConfirm({
|
||||
desc: t('deleteAssistant'),
|
||||
desc: t('确认删除该助手?'),
|
||||
okTxt: t('delete'),
|
||||
onOk(next) {
|
||||
deleteAssistantApi(data.id).then(() => reload())
|
||||
@@ -42,10 +69,104 @@ export default function Assistants() {
|
||||
})
|
||||
}
|
||||
|
||||
const render = (item: any) => {
|
||||
const [openSwitch, setOpenSwitch] = useState(item.status === 1);
|
||||
const handleChange = (bln) => {
|
||||
// captureAndAlertRequestErrorHoc(updataOnlineState(item.id, item, bln).then(res => {
|
||||
// setOpenSwitch(bln);
|
||||
// if (res) {
|
||||
// refreshData((item1) => item1.id === item.id, { status: bln ? 1 : 0 })
|
||||
// }
|
||||
// return res
|
||||
// }))
|
||||
return captureAndAlertRequestErrorHoc(changeAssistantStatusApi(item.id, bln ? 1 : 0)).then(res => {
|
||||
if (res === null) {
|
||||
refreshData((item1) => item1.id === item.id, { status: bln ? 1 : 0 })
|
||||
}
|
||||
return res
|
||||
})
|
||||
};
|
||||
return(
|
||||
<Flexbox align={'flex-start'}>
|
||||
{item.type == 0 ?
|
||||
<div className="selectNpcFlexboxZiDing" onClick={() => setOpen(true)}>
|
||||
<div>
|
||||
<img src={zidingyijia} alt="" />
|
||||
<span>自定义NPC</span>
|
||||
</div>
|
||||
<p>通过描述角色和任务来创建你的NPC,NPC 可以调用多个能力和工具。</p>
|
||||
<div className="flex justify-end mb-[7px] mr-[14px] mt-0">
|
||||
<img src={zidingyi1} className="w-[68px]" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
// <DialogForceUpdate
|
||||
// trigger={
|
||||
// <CardComponent<FlowType>
|
||||
// data={null}
|
||||
// type='skill'
|
||||
// title={t('build.createAssistant')}
|
||||
// description={(<>
|
||||
// <p>{t('build.createDescription')}</p>
|
||||
// <p>{t('build.nextDescription')}</p>
|
||||
// </>)}
|
||||
// onClick={() => console.log('新建')}
|
||||
// ></CardComponent>
|
||||
// }>
|
||||
// <CreateAssistant ></CreateAssistant>
|
||||
// </DialogForceUpdate>
|
||||
:
|
||||
<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=""/>}
|
||||
</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=""/>}
|
||||
{/* <img src={robot} className="w-[40px]" alt=""/> */}
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.desc}</p>
|
||||
<div>
|
||||
<div>
|
||||
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => item.status !== 1 && navigate('/assistant/' + item.id)}>
|
||||
<img src={bianji} className="w-[14px] mr-[10px]" alt=""/>
|
||||
编辑
|
||||
</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)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch checked={openSwitch} onCheckedChange={handleChange} />
|
||||
</div>
|
||||
</div>}
|
||||
</Flexbox>
|
||||
)
|
||||
};
|
||||
|
||||
return <div className="h-full relative">
|
||||
<div className="px-10 py-10 h-full overflow-y-scroll scrollbar-hide relative top-[-60px]">
|
||||
<div className="flex">
|
||||
<SearchInput className="w-64" placeholder={t('build.searchAssistant')} onChange={(e) => search(e.target.value)}></SearchInput>
|
||||
<div className="px-[27px] relative" style={{ height: 'calc(100% - 50px)' }}>
|
||||
<p className="text-[11px]" style={{color:"#666666"}}>在此页面管理您的NPC,对NPC上下线、编辑等等</p>
|
||||
<div className="flex gap-2 absolute top-[-47px] right-[27px] btn-r">
|
||||
<div>
|
||||
|
||||
<img ref={inputRef} src={sousuo} alt="" />
|
||||
<input type="text" placeholder="搜索" onChange={(e) => search(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
{/* list */}
|
||||
{
|
||||
@@ -53,9 +174,9 @@ export default function Assistants() {
|
||||
? <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-[rgba(255,255,255,0.6)] dark:bg-blur-shared">
|
||||
<span className="loading loading-infinity loading-lg"></span>
|
||||
</div>
|
||||
: <div className="mt-6 flex gap-2 flex-wrap pb-20 min-w-[980px]">
|
||||
: <div className="Skill_box overflow-y-scroll scrollbar-hide">
|
||||
{/* 创建助手 */}
|
||||
<DialogForceUpdate
|
||||
{/* <DialogForceUpdate
|
||||
trigger={
|
||||
<CardComponent<FlowType>
|
||||
data={null}
|
||||
@@ -69,8 +190,8 @@ export default function Assistants() {
|
||||
></CardComponent>
|
||||
}>
|
||||
<CreateAssistant ></CreateAssistant>
|
||||
</DialogForceUpdate>
|
||||
{
|
||||
</DialogForceUpdate> */}
|
||||
{/* {
|
||||
dataSource.map((item, i) => (
|
||||
<CardComponent<AssistantItemDB>
|
||||
data={item}
|
||||
@@ -88,14 +209,25 @@ export default function Assistants() {
|
||||
onCheckedChange={handleCheckedChange}
|
||||
></CardComponent>
|
||||
))
|
||||
}
|
||||
} */}
|
||||
<SpotlightCard items={dataSource} renderItem={render} className="mt-[14px] selectNpcSpotlightCard"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* footer */}
|
||||
<div className="flex justify-between absolute bottom-0 left-0 w-full bg-background-main h-16 items-center px-10">
|
||||
<p className="text-sm text-muted-foreground break-keep">{t('build.manageAssistant')}</p>
|
||||
<div className="flex justify-end absolute bottom-0 left-0 w-full bg-background-main h-16 items-center px-10 bg-[#000000] z-[9]">
|
||||
{/* <p className="text-sm text-muted-foreground break-keep">{t('build.manageAssistant')}</p> */}
|
||||
<AutoPagination className="m-0 w-auto justify-end" page={page} pageSize={pageSize} total={total} onChange={setPage}></AutoPagination>
|
||||
</div>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{/* <DialogTrigger asChild>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
{open ? children : null} */}
|
||||
<CreateAssistant ></CreateAssistant>
|
||||
</Dialog>
|
||||
{/* 分享链接 */}
|
||||
<ShareLink isShareLink={isShareLink} setIsShareLink={setIsShareLink} data={isShareLinkData}></ShareLink>
|
||||
</div>
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import SkillTempSheet from "@/components/bs-comp/sheets/SkillTempSheet";
|
||||
import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
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";
|
||||
@@ -28,9 +28,14 @@ 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 zidingyijia from "../../assets/npc/zidingyijia.png";
|
||||
import ShareLink from "./externalUse/shareLink";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import Templates from "./temps";
|
||||
import { Size } from "recharts/types/util/types";
|
||||
import SkillTemps from "./components/SkillTemps";
|
||||
|
||||
export default function Skills() {
|
||||
const { t } = useTranslation()
|
||||
@@ -41,9 +46,19 @@ export default function Skills() {
|
||||
const { page, pageSize, data: dataSource, total, loading, setPage, search, reload, refreshData } = useTable<FlowType>({ pageSize: 14 }, (param) =>
|
||||
readFlowsFromDatabase(param.page, param.pageSize, param.keyword)
|
||||
)
|
||||
|
||||
if(dataSource[0] && dataSource[0].type != 0){
|
||||
dataSource.unshift({"type":0})
|
||||
}
|
||||
|
||||
const inputRef = useRef(null);
|
||||
const [open, setOpen] = useState(false)
|
||||
const [isTempsPage, setIsTempPage] = useState(false)
|
||||
const [temps, loadTemps] = useTemps()
|
||||
|
||||
const { open: tempOpen, flowRef, toggleTempModal } = useCreateTemp()
|
||||
const [isShareLink, setIsShareLink] = useState(false)
|
||||
const [isShareLinkData, setIsShareLinkData] = useState("Any");
|
||||
|
||||
// 上下线
|
||||
const handleCheckedChange = (checked, data) => {
|
||||
@@ -75,7 +90,7 @@ export default function Skills() {
|
||||
|
||||
// 选模板(创建技能)
|
||||
const handldSelectTemp = async (tempId) => {
|
||||
const [flow] = await readTempsDatabase(tempId)
|
||||
const [flow] = await readTempsDatabase(tempId.id)
|
||||
|
||||
flow.name = `${flow.name}-${generateUUID(5)}`
|
||||
captureAndAlertRequestErrorHoc(saveFlowToDatabase({ ...flow, id: flow.flow_id }).then(res => {
|
||||
@@ -86,77 +101,88 @@ export default function Skills() {
|
||||
}))
|
||||
}
|
||||
|
||||
const nvaigate = useNavigate()
|
||||
const handleEdit = (id,pageNo) => {
|
||||
// onBeforeEdit?.()
|
||||
window.SearchSkillsPage = { no: pageNo, key: inputRef.current.value };
|
||||
nvaigate("/skill/" + id)
|
||||
}
|
||||
|
||||
// 模板管理
|
||||
if (isTempsPage) return <Templates onBack={() => setIsTempPage(false)} onChange={loadTemps}></Templates>
|
||||
|
||||
const render = (item: any) => {
|
||||
const [openSwitch, setOpenSwitch] = useState(item.status === 2);
|
||||
const handleChange = (bln) => {
|
||||
captureAndAlertRequestErrorHoc(updataOnlineState(item.id, item, bln).then(res => {
|
||||
setOpenSwitch(bln);
|
||||
loadPage(page.pageNo);
|
||||
if (res) {
|
||||
refreshData((item1) => item1.id === item.id, { status: bln ? 2 : 1 })
|
||||
}
|
||||
return res
|
||||
// loadPage(page.pageNo);
|
||||
// itema.status = bln ? 2 : 1
|
||||
}))
|
||||
};
|
||||
return(
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox`}>
|
||||
{/* <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>
|
||||
{(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=""/>}
|
||||
{/* <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-[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=""/>}
|
||||
{/* <img src={robot} className="w-[40px]" alt=""/> */}
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.description}</p>
|
||||
<div>
|
||||
<Flexbox align={'flex-start'}>
|
||||
{item.type == 0 ?
|
||||
<div className="selectNpcFlexboxZiDing" onClick={() => setOpen(true)}>
|
||||
<div>
|
||||
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => handleEdit(item.id,page.pageNo)}>
|
||||
<img src={bianji} className="w-[14px] mr-[10px]" alt=""/>
|
||||
编辑
|
||||
</div>}
|
||||
|
||||
{user.role === 'admin' && <div className="w-[27px] h-[27px] create mr-[14px]" onClick={() => toggleTempModal(item)}>
|
||||
<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)}}>
|
||||
<img src={share} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
<img src={zidingyijia} alt="" />
|
||||
<span>自定义能力</span>
|
||||
</div>
|
||||
<Switch checked={openSwitch} onCheckedChange={handleChange} />
|
||||
</div>
|
||||
<div className="del" onClick={() => handleDelete(item.id)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
<p>能力通过可视化的流程编排,明确工作执行 步骤。我们提供场景模板供您使用和参考。</p>
|
||||
<img src={zidingyi1} alt="" />
|
||||
</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=""/>}
|
||||
</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=""/>}
|
||||
{/* <img src={robot} className="w-[40px]" alt=""/> */}
|
||||
<p className="ml-[14px]">{item.name}</p>
|
||||
</div>
|
||||
<p>{item.description}</p>
|
||||
<div>
|
||||
<div>
|
||||
{!openSwitch && <div className="w-[80px] h-[27px] bianji mr-[14px]" onClick={() => handleEdit(item.id,page.pageNo)}>
|
||||
<img src={bianji} className="w-[14px] mr-[10px]" alt=""/>
|
||||
编辑
|
||||
</div>}
|
||||
|
||||
{user.role === 'admin' && <div className="w-[27px] h-[27px] create mr-[14px]" onClick={() => toggleTempModal(item)}>
|
||||
<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)}}>
|
||||
<img src={share} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
|
||||
<div className="w-[27px] h-[27px] create" onClick={() => handleDelete(item)}>
|
||||
<img src={del} className="w-[14px]" alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch checked={openSwitch} onCheckedChange={handleChange} />
|
||||
</div>
|
||||
</div>}
|
||||
</Flexbox>
|
||||
)
|
||||
};
|
||||
|
||||
return <div className="h-full relative">
|
||||
<div className="px-[27px] h-full relative">
|
||||
<div className="px-[27px] relative" style={{ height: 'calc(100% - 50px)' }}>
|
||||
<p className="text-[11px]" style={{color:"#666666"}}>在此页面管理能力,对能力上下线、编辑等等</p>
|
||||
<div className="flex gap-2 absolute top-[-47px] right-[27px] btn-r">
|
||||
{/* <SearchInput className="w-64" placeholder={t('skills.skillSearch')} onChange={(e) => search(e.target.value)}></SearchInput> */}
|
||||
@@ -166,10 +192,12 @@ export default function Skills() {
|
||||
onClick={() => navigate('/build/temps')}
|
||||
><MoveOneIcon />{t('skills.manageTemplate')}</Button>} */}
|
||||
<div>
|
||||
|
||||
<img src={sousuo} alt="" />
|
||||
<input type="text" placeholder="搜索" onChange={(e) => search(e.target.value)} />
|
||||
<input ref={inputRef} type="text" placeholder="搜索" onChange={(e) => search(e.target.value)} />
|
||||
</div>
|
||||
{user.role === 'admin' && <div onClick={() => setIsTempPage(true)}><img src={moban} className="w-[14px] mr-[5px] btn-r-d" alt=""/><span>模版管理</span></div>}
|
||||
{user.role === 'admin' && <div onClick={() => navigate('/build/temps')}><img src={moban} className="w-[14px] mr-[5px] btn-r-d" alt=""/><span>模版管理</span></div>}
|
||||
{/* {user.role === 'admin' && <div onClick={() => setIsTempPage(true)}><img src={moban} className="w-[14px] mr-[5px] btn-r-d" alt=""/><span>模版管理</span></div>} */}
|
||||
</div>
|
||||
{/* list */}
|
||||
{
|
||||
@@ -221,13 +249,17 @@ export default function Skills() {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* chose template */}
|
||||
<SkillTemps flows={temps} isTemp open={open} setOpen={setOpen} onSelect={handldSelectTemp}></SkillTemps>
|
||||
{/* 添加模板 */}
|
||||
<CreateTemp flow={flowRef.current} open={tempOpen} setOpen={() => toggleTempModal()} onCreated={() => { }} ></CreateTemp>
|
||||
{/* footer */}
|
||||
<div className="flex justify-between absolute bottom-0 left-0 w-full bg-background-main h-16 items-center px-10">
|
||||
<p className="text-sm text-muted-foreground break-keep">{t('skills.manageProjects')}</p>
|
||||
<div className="flex justify-end absolute bottom-0 left-0 w-full bg-background-main h-16 items-center px-10 bg-[#000000] z-[9]">
|
||||
{/* <p className="text-sm text-muted-foreground break-keep">{t('skills.manageProjects')}</p> */}
|
||||
<AutoPagination className="m-0 w-auto justify-end" page={page} pageSize={pageSize} total={total} onChange={setPage}></AutoPagination>
|
||||
</div>
|
||||
{/* 分享链接 */}
|
||||
<ShareLink isShareLink={isShareLink} setIsShareLink={setIsShareLink} data={isShareLinkData}></ShareLink>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -244,4 +276,19 @@ const useCreateTemp = () => {
|
||||
setOpen(!open)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模板数据
|
||||
const useTemps = () => {
|
||||
const [temps, setTemps] = useState([]);
|
||||
|
||||
const loadTemps = () => {
|
||||
readTempsDatabase().then(setTemps);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadTemps();
|
||||
}, []);
|
||||
|
||||
return [temps, loadTemps];
|
||||
};
|
||||
@@ -7,6 +7,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EditTool from "./components/EditTool";
|
||||
import ToolItem from "./components/ToolItem";
|
||||
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";
|
||||
|
||||
export default function tabTools({ select = null, onSelect }) {
|
||||
const [keyword, setKeyword] = useState(" ");
|
||||
@@ -35,40 +39,54 @@ export default function tabTools({ select = null, onSelect }) {
|
||||
|
||||
return (
|
||||
<div className="flex h-full relative" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-full flex h-full overflow-y-scroll scrollbar-hide relative top-[-60px]">
|
||||
<div className="w-fit p-6">
|
||||
<h1>{t("tools.addTool")}</h1>
|
||||
<SearchInput
|
||||
placeholder={t("tools.search")}
|
||||
className="mt-6"
|
||||
<p className="absolute text-[11px] px-[27px]" style={{color:"#666666"}}>在此页面管理您的自定义工具,对自定义工具创建、编辑等等</p>
|
||||
<div className="mt-[24px] w-full flex overflow-y-scroll scrollbar-hide relative bg-[#121212] ml-[14px]" style={{ height: 'calc(100vh - 110px)' }}>
|
||||
<div className="w-[260px]">
|
||||
{/* <h1>{t("tools.addTool")}</h1> */}
|
||||
{/* <SearchInput
|
||||
placeholder="搜索"
|
||||
className="w-[237px] h-[34px] bg-[#1A1A1A] ml-[14px] mt-[14px] text-[#666666]"
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
/> */}
|
||||
<div className="relative ml-[14px] mt-[14px]">
|
||||
<img src={sousuo} className="absolute w-[14px] left-[14px] top-[10px]" alt="" />
|
||||
<input placeholder="搜索"
|
||||
className="w-[237px] h-[34px] bg-[#1A1A1A] text-[#fff] pl-[40px]"
|
||||
style={{borderRadius:"17px",outline:"none"}}
|
||||
onChange={(e) => setKeyword(e.target.value)} type="text" />
|
||||
</div>
|
||||
{/* <Button
|
||||
className="mt-4 w-full"
|
||||
onClick={() => editRef.current.open()}
|
||||
>
|
||||
{t('create')}{t("tools.createCustomTool")}
|
||||
</Button>
|
||||
<div className="mt-4">
|
||||
</Button> */}
|
||||
<div className="w-[237px] h-[27px] bg-[#FFD025] m-[14px] flex justify-center items-center border-radius-14 cursor-pointer" onClick={() => editRef.current.open()}>
|
||||
<img src={gongjuAdd} className="w-[14px]" alt="" />
|
||||
<span className="text-[#333333] ml-[12px]">自定义工具</span>
|
||||
</div>
|
||||
<div className="mt-[14px]">
|
||||
<div
|
||||
className={`flex cursor-pointer items-center gap-2 rounded-md px-4 py-2 transition-all duration-200 hover:bg-muted-foreground/10 ${type === "" && "bg-muted-foreground/10"
|
||||
className={`flex cursor-pointer items-center w-[237px] h-[40px] ml-[14px] border-radius-7 pl-[14px] ${type === "" && "bg-[#2A271D] text-[#FFD54C]"
|
||||
}`}
|
||||
onClick={() => setType("")}
|
||||
>
|
||||
<PersonIcon />
|
||||
<span>{t("tools.builtinTools")}</span>
|
||||
{type === "" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <PersonIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">内置工具</span>
|
||||
</div>
|
||||
<div
|
||||
className={`mt-1 flex cursor-pointer items-center gap-2 rounded-md px-4 py-2 transition-all duration-200 hover:bg-muted-foreground/10 ${type === "edit" && "bg-muted-foreground/10"
|
||||
className={`flex cursor-pointer items-center w-[237px] h-[40px] ml-[14px] border-radius-7 pl-[14px] ${type === "edit" && "bg-[#2A271D] text-[#FFD54C]"
|
||||
}`}
|
||||
onClick={() => setType("edit")}
|
||||
>
|
||||
<StarFilledIcon />
|
||||
<span>{t("tools.customTools")}</span>
|
||||
{type === "edit" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <StarFilledIcon /> */}
|
||||
<span className="ml-[8px] text-[#999999]">自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full flex-1 overflow-auto bg-[#fff] p-5 pt-12 scrollbar-hide">
|
||||
<div className="h-full w-full flex-1 overflow-auto p-5 pt-12 scrollbar-hide">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{options.length ? (
|
||||
options.map((el) => (
|
||||
@@ -90,11 +108,11 @@ export default function tabTools({ select = null, onSelect }) {
|
||||
</div>
|
||||
</div>
|
||||
{/* footer */}
|
||||
<div className="absolute bottom-0 left-0 flex h-16 w-full items-center justify-between bg-[#F4F5F8] px-10">
|
||||
{/* <div className="absolute bottom-0 left-0 flex h-16 w-full items-center justify-between bg-[#F4F5F8] px-10">
|
||||
<p className="break-keep text-sm text-muted-foreground">
|
||||
{t("tools.manageCustomTools")}
|
||||
</p>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<EditTool onReload={() => {
|
||||
// 切换自定义工具 并 刷新
|
||||
|
||||
@@ -17,8 +17,12 @@ import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import robot from "../../assets/robot.png";
|
||||
import del from "../../assets/npc/del.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function Templates({ onBack, onChange }) {
|
||||
export default function Templates() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onChange = () => { }
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [temps, setTemps] = useState([])
|
||||
@@ -107,7 +111,7 @@ export default function Templates({ onBack, onChange }) {
|
||||
</Flexbox>
|
||||
);
|
||||
return <div className="p-6 h-screen overflow-y-auto temps">
|
||||
<div className="fanhui" onClick={onBack}>
|
||||
<div className="fanhui" onClick={() => navigate('/build/skills')}>
|
||||
返 回
|
||||
</div>
|
||||
{/* <p className="text-gray-500">{t('skills.skillTemplateManagement')}</p> */}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Report from "./pages/Report";
|
||||
import SkillChatPage from "./pages/ChatAppPage";
|
||||
import ChatShare from "./pages/ChatAppPage/chatShare";
|
||||
import SkillAssisPage from "./pages/SkillPage/tabAssistant";
|
||||
import EditAssistantPage from "./pages/SkillPage/editAssistant";
|
||||
import SkillsPage from "./pages/SkillPage/tabSkills";
|
||||
import SkillToolsPage from "./pages/SkillPage/tabTools";
|
||||
import SkillPage from "./pages/SkillPage";
|
||||
@@ -18,6 +19,7 @@ import SkillPage from "./pages/SkillPage";
|
||||
import L2Edit from "./pages/SkillPage/l2Edit";
|
||||
import SystemPage from "./pages/SystemPage";
|
||||
import BuildLayout from "./layout/BuildLayout";
|
||||
import Templates from "./pages/SkillPage/temps";
|
||||
|
||||
// react 与 react router dom版本不匹配
|
||||
// const FileLibPage = lazy(() => import(/* webpackChunkName: "FileLibPage" */ "./pages/FileLibPage"));
|
||||
@@ -47,6 +49,9 @@ const router = createBrowserRouter([
|
||||
{ path: "", element: <Navigate to="assist" replace /> },
|
||||
]
|
||||
},
|
||||
{ path: "build/skill", element: <L2Edit /> },
|
||||
{ path: "build/skill/:id/:vid", element: <L2Edit /> },
|
||||
{ path: "build/temps", element: <Templates /> },
|
||||
{ path: "model", element: <ModelPage /> },
|
||||
{ path: "sys", element: <SystemPage /> },
|
||||
],
|
||||
@@ -58,6 +63,12 @@ const router = createBrowserRouter([
|
||||
{ path: "", element: <FlowPage /> }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/assistant/:id/",
|
||||
children: [
|
||||
{ path: "", element: <EditAssistantPage /> }
|
||||
]
|
||||
},
|
||||
// 独立会话页
|
||||
{ path: "/chat", element: <SkillChatPage /> },
|
||||
{ path: "/chat/:id/", element: <ChatShare /> },
|
||||
|
||||
77
src/store/assistantStore.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { AssistantDetail } from '@/types/assistant'
|
||||
import { create } from 'zustand'
|
||||
import { getAssistantDetailApi } from '../controllers/API/assistant'
|
||||
|
||||
/**
|
||||
* 助手编辑管理
|
||||
*/
|
||||
|
||||
type State = {
|
||||
changed: boolean,
|
||||
assistantState: AssistantDetail
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
dispatchAssistant: (action: Action, assistantState: Partial<AssistantDetail>) => void,
|
||||
loadAssistantState: (id: string) => Promise<any>
|
||||
saveAfter: () => void
|
||||
destroy: () => void
|
||||
}
|
||||
|
||||
type Action = 'setBaseInfo' | 'setting' | 'setPrompt' | 'setGuideword' | 'setTools' | 'setFlows' | 'setQuestion'
|
||||
|
||||
const assistantReducer = (state: State, action: Action, data: Partial<AssistantDetail>) => {
|
||||
console.log('action :>> ', action, data);
|
||||
return { changed: true, assistantState: { ...state.assistantState, ...data } }
|
||||
// switch (action) {
|
||||
// case 'setBaseInfo':
|
||||
// return { assistantState: { ...state.assistantState, ...data } }
|
||||
// default:
|
||||
// return state
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
const assistantTemp = {
|
||||
id: 3,
|
||||
name: "",
|
||||
desc: "",
|
||||
logo: "",
|
||||
prompt: "",
|
||||
guide_word: "",
|
||||
guide_question: [],
|
||||
model_name: "",
|
||||
temperature: 0.5,
|
||||
status: 0,
|
||||
user_id: 1,
|
||||
create_time: "",
|
||||
update_time: "",
|
||||
tool_list: [],
|
||||
flow_list: [],
|
||||
knowledge_list: [],
|
||||
}
|
||||
|
||||
export const useAssistantStore = create<State & Actions>((set) => ({
|
||||
changed: false,
|
||||
assistantState: { ...assistantTemp },
|
||||
dispatchAssistant: (action: Action, data: Partial<AssistantDetail>) => set((state) => assistantReducer(state, action, data)),
|
||||
// 加载助手状态
|
||||
loadAssistantState: (id) => {
|
||||
return getAssistantDetailApi(id).then(data => {
|
||||
set({
|
||||
assistantState: {
|
||||
...data,
|
||||
// 补一个空行
|
||||
guide_question: data.guide_question ? [...data.guide_question, ''] : ['']
|
||||
}
|
||||
})
|
||||
return data
|
||||
})
|
||||
},
|
||||
saveAfter() {
|
||||
set({ changed: false })
|
||||
},
|
||||
destroy: () => {
|
||||
set({ assistantState: { ...assistantTemp } })
|
||||
}
|
||||
}))
|
||||
267
src/store/diffFlowStore.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* 技能组件版本效果对比
|
||||
*/
|
||||
import { generateUUID } from "@/components/bs-ui/utils"
|
||||
import { getVersionDetails, runTestCase } from "@/controllers/API/flow"
|
||||
import { captureAndAlertRequestErrorHoc } from "@/controllers/request"
|
||||
import { create } from "zustand"
|
||||
|
||||
const enum RunningType {
|
||||
/** 全量对比 */
|
||||
All = 'all',
|
||||
/** 列对比 */
|
||||
Col = 'col',
|
||||
/** 行对比 */
|
||||
Row = 'row',
|
||||
/** 无对比 */
|
||||
None = ''
|
||||
}
|
||||
|
||||
type State = {
|
||||
mulitVersionFlow: any[],
|
||||
/** 用例问题列表 */
|
||||
questions: { id: string, q: string, ready: boolean }[],
|
||||
/** 运行过一次 */
|
||||
running: boolean,
|
||||
/** 测试运行按钮状态 */
|
||||
runningType: RunningType,
|
||||
/** 版本运行按钮状态 */
|
||||
readyVersions: { [verstion in string]: boolean }
|
||||
/** 单元格ref */
|
||||
cellRefs: { [key in string]: any }
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
/** 初始化版本技能 */
|
||||
initFristVersionFlow(versionId: string): void,
|
||||
/** 添加空的对比版本 */
|
||||
addEmptyVersionFlow(): void,
|
||||
/** 添加对比版本技能 */
|
||||
addVersionFlow(versionId: string, index: number): void,
|
||||
/** 移除对比版本技能 */
|
||||
removeVersionFlow(index: number): void,
|
||||
/** 更新版本运行状态 */
|
||||
// updateReadyVersions(version: string): void,
|
||||
/** 上传覆盖问题列表 */
|
||||
overQuestions(list: string[]): void,
|
||||
/** 添加问题 */
|
||||
addQuestion(q: string): void
|
||||
}
|
||||
|
||||
export const useDiffFlowStore = create<State & Actions>((set, get) => ({
|
||||
mulitVersionFlow: [],
|
||||
questions: [],
|
||||
readyVersions: {},
|
||||
running: false,
|
||||
runningType: RunningType.None,
|
||||
cellRefs: {},
|
||||
initFristVersionFlow(versionId) {
|
||||
getVersionDetails(versionId).then(version => {
|
||||
set({
|
||||
mulitVersionFlow: [version, null],
|
||||
questions: [],
|
||||
readyVersions: {},
|
||||
running: false,
|
||||
runningType: RunningType.None,
|
||||
cellRefs: {},
|
||||
})
|
||||
})
|
||||
},
|
||||
addEmptyVersionFlow() {
|
||||
set((state) => ({ mulitVersionFlow: [...state.mulitVersionFlow, null] }))
|
||||
},
|
||||
addVersionFlow(versionId, index) {
|
||||
const { running, readyVersions } = get()
|
||||
// 标记可运行状态
|
||||
if (running) {
|
||||
set({ readyVersions: { ...readyVersions, [versionId]: true } })
|
||||
}
|
||||
|
||||
getVersionDetails(versionId).then(version => {
|
||||
set((state) => {
|
||||
// 填充flow
|
||||
state.mulitVersionFlow[index] = version
|
||||
return { mulitVersionFlow: [...state.mulitVersionFlow] }
|
||||
})
|
||||
})
|
||||
},
|
||||
removeVersionFlow(index) {
|
||||
set((state) => ({
|
||||
mulitVersionFlow:
|
||||
state.mulitVersionFlow.filter((_, i) => i !== index)
|
||||
}))
|
||||
},
|
||||
// updateReadyVersions(version) {
|
||||
// if (get().running) {
|
||||
// set((state) => ({
|
||||
// readyVersions: {
|
||||
// ...state.readyVersions,
|
||||
// [version]: true
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
// },
|
||||
overQuestions(list) {
|
||||
set(() => ({
|
||||
questions: list.splice(0, 20).map(q => ({
|
||||
id: generateUUID(5),
|
||||
q,
|
||||
ready: get().running
|
||||
}))
|
||||
}))
|
||||
},
|
||||
addQuestion(q) {
|
||||
set((state) => ({
|
||||
questions: [...state.questions, { q, id: generateUUID(5), ready: get().running }]
|
||||
}))
|
||||
},
|
||||
updateQuestion(q, index) {
|
||||
set((state) => ({
|
||||
questions: state.questions.map((el, i) => i === index ? { ...el, q, ready: get().running } : el)
|
||||
}))
|
||||
},
|
||||
removeQuestion(index) {
|
||||
set((state) => ({
|
||||
questions: state.questions.filter((_, i) => i !== index)
|
||||
}))
|
||||
},
|
||||
addCellRef: (key, ref) => {
|
||||
set(state => {
|
||||
return { cellRefs: { ...state.cellRefs, [key]: ref } };
|
||||
})
|
||||
},
|
||||
removeCellRef: (key) => set(state => {
|
||||
const newCellRefs = { ...state.cellRefs }
|
||||
delete newCellRefs[key]
|
||||
return { cellRefs: newCellRefs };
|
||||
}),
|
||||
async allRunStart(nodeId, inputs) {
|
||||
set((state) => ({
|
||||
readyVersions: {},
|
||||
questions: state.questions.map(el => ({ ...el, ready: false })),
|
||||
runningType: RunningType.All,
|
||||
running: true
|
||||
}))
|
||||
const questions = get().questions
|
||||
const versions = get().mulitVersionFlow
|
||||
|
||||
await runTest({
|
||||
questions,
|
||||
questionIndexs: questions.map((_, index) => index),
|
||||
versionIds: versions.filter(el => el).map(version => version?.id),
|
||||
nodeId,
|
||||
inputs,
|
||||
refs: get().cellRefs
|
||||
})
|
||||
set({ runningType: RunningType.None })
|
||||
},
|
||||
async rowRunStart(qIndex, nodeId, inputs) {
|
||||
set((state) => ({
|
||||
questions: state.questions.map((el, i) => qIndex === i ? { ...el, ready: false } : el),
|
||||
runningType: RunningType.Row
|
||||
}))
|
||||
const questions = get().questions
|
||||
const versions = get().mulitVersionFlow
|
||||
|
||||
await runTest({
|
||||
questions,
|
||||
questionIndexs: [qIndex],
|
||||
versionIds: versions.filter(el => el).map(version => version?.id),
|
||||
nodeId,
|
||||
inputs,
|
||||
refs: get().cellRefs
|
||||
})
|
||||
set({ runningType: RunningType.None })
|
||||
},
|
||||
async colRunStart(versionId, nodeId, inputs) {
|
||||
set((state) => ({
|
||||
readyVersions: { ...state.readyVersions, [versionId]: false },
|
||||
runningType: RunningType.Col
|
||||
}))
|
||||
const questions = get().questions
|
||||
|
||||
await runTest({
|
||||
questions,
|
||||
questionIndexs: questions.map((_, index) => index),
|
||||
versionIds: [versionId],
|
||||
nodeId,
|
||||
inputs,
|
||||
refs: get().cellRefs
|
||||
})
|
||||
set({ runningType: RunningType.None })
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* 运行测试用例
|
||||
* @param questions 所有问题列表
|
||||
* @param questionIndexs 问题索引
|
||||
* @param nodeId 节点id
|
||||
* @param versionIds 版本id
|
||||
* @param refs 单元格ref
|
||||
*/
|
||||
const runTest = ({ questions, questionIndexs, nodeId, versionIds, inputs, refs }) => {
|
||||
// loading
|
||||
// console.log(refs, 222);
|
||||
const runIds = []
|
||||
questionIndexs.forEach(qIndex => {
|
||||
versionIds.forEach(versionId => {
|
||||
refs[`${qIndex}-${versionId}`].current.loading()
|
||||
runIds.push(`${qIndex}-${versionId}`)
|
||||
})
|
||||
});
|
||||
// 运行
|
||||
const data = JSON.stringify({
|
||||
question_list: questionIndexs.map(qIndex => questions[qIndex].q),
|
||||
version_list: versionIds,
|
||||
inputs,
|
||||
node_id: nodeId
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const apiUrl = `/api/v1/flows/compare/stream?data=${encodeURIComponent(data)}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
const { type, question_index, version_id, answer } = parsedData;
|
||||
if (!type) {
|
||||
refs[`${questionIndexs[question_index]}-${version_id}`].current.setData(answer)
|
||||
} else if (type === 'end') {
|
||||
resolve('')
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error('event :>> ', error);
|
||||
eventSource.close();
|
||||
runIds.forEach(id => {
|
||||
refs[id].current.loaded()
|
||||
})
|
||||
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
// runTestCaseStream()
|
||||
// return captureAndAlertRequestErrorHoc(runTestCase({
|
||||
// question_list: questionIndexs.map(qIndex => questions[qIndex].q),
|
||||
// version_list: versionIds,
|
||||
// inputs,
|
||||
// node_id: nodeId
|
||||
// }).then(data => {
|
||||
// data.forEach((row, rowIndex) => {
|
||||
// Object.keys(row).forEach(vId => {
|
||||
// refs[`${questionIndexs[rowIndex]}-${vId}`].current.setData(row[vId])
|
||||
// })
|
||||
// })
|
||||
// }), () => {
|
||||
// // error callback
|
||||
// runIds.forEach(id => {
|
||||
// refs[id].current.loaded()
|
||||
// })
|
||||
// })
|
||||
}
|
||||
@@ -90,7 +90,7 @@
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
.xinDuiHua-btn{
|
||||
width: 204px;
|
||||
width: 260px;
|
||||
height: 35px;
|
||||
background: #0D0D0D;
|
||||
border-radius: 18px;
|
||||
@@ -2439,7 +2439,9 @@
|
||||
.border-radius-14{
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.border-radius-7{
|
||||
border-radius: 7px;
|
||||
}
|
||||
.skillSettings{
|
||||
width: 542px;
|
||||
margin: 0 auto;
|
||||
@@ -2575,6 +2577,8 @@
|
||||
}
|
||||
}
|
||||
.Skill_box{
|
||||
height: 100%;
|
||||
padding-bottom: 30px;
|
||||
.css-15l7r2q{
|
||||
gap: 36px!important;
|
||||
}
|
||||
@@ -2597,10 +2601,44 @@
|
||||
background: #111111;
|
||||
}
|
||||
}
|
||||
.selectNpcFlexboxZiDing{
|
||||
height: 176px;
|
||||
>div{
|
||||
margin-left: 14px;
|
||||
margin-top: 27px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
>img{
|
||||
width: 15px;
|
||||
}
|
||||
>span{
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: #FFFFFF;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
>p{
|
||||
margin-top: 20px;
|
||||
margin-left: 14px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
>img{
|
||||
width: 68px;
|
||||
margin: 7px 0;
|
||||
margin-left: 180px;
|
||||
}
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
height: 176px;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content:space-between;
|
||||
/* padding: 14px; */
|
||||
position: relative;
|
||||
padding-bottom: 55px;
|
||||
@@ -2664,7 +2702,8 @@
|
||||
}
|
||||
}
|
||||
.npcInfoItemBg{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
height: 64px;
|
||||
margin-bottom: -56px;
|
||||
@@ -3314,7 +3353,8 @@
|
||||
}
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
height: 127px;
|
||||
// height: 127px;
|
||||
padding-bottom: 14px;
|
||||
display: block;
|
||||
/* padding: 14px; */
|
||||
>div:nth-of-type(2){
|
||||
@@ -3804,4 +3844,250 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.npcInput{
|
||||
// margin-left: 14px;
|
||||
// width: 89%!important;
|
||||
// height: 34px!important;
|
||||
background: #1A1A1A!important;
|
||||
border-radius: 7px!important;
|
||||
outline: none!important;
|
||||
--tw-ring-color:none!important;
|
||||
border: none!important;
|
||||
--tw-ring-offset-color:none!important;
|
||||
color: #fff!important;
|
||||
|
||||
&::-webkit-input-placeholder{
|
||||
color: #666!important;
|
||||
}
|
||||
// outline: #997e1f;
|
||||
&:focus{
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
.npcInput1{
|
||||
background: #262626!important;
|
||||
border-radius: 7px!important;
|
||||
outline: none!important;
|
||||
--tw-ring-color:none!important;
|
||||
border: none!important;
|
||||
--tw-ring-offset-color:none!important;
|
||||
color: #fff!important;
|
||||
|
||||
&::-webkit-input-placeholder{
|
||||
color: #666!important;
|
||||
}
|
||||
// outline: #997e1f;
|
||||
&:focus{
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
.npcInput2{
|
||||
background: #1a1a1a!important;
|
||||
border-radius: 7px!important;
|
||||
outline: none!important;
|
||||
--tw-ring-color:none!important;
|
||||
border: none!important;
|
||||
--tw-ring-offset-color:none!important;
|
||||
color: #fff!important;
|
||||
|
||||
&::-webkit-input-placeholder{
|
||||
color: #666!important;
|
||||
}
|
||||
// outline: #997e1f;
|
||||
&:focus{
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
.npcInput3{
|
||||
background: #0F0F0F!important;
|
||||
border-radius: 17px!important;
|
||||
outline: none!important;
|
||||
--tw-ring-color:none!important;
|
||||
border: none!important;
|
||||
--tw-ring-offset-color:none!important;
|
||||
color: #fff!important;
|
||||
|
||||
&::-webkit-input-placeholder{
|
||||
color: #666!important;
|
||||
}
|
||||
// outline: #997e1f;
|
||||
&:focus{
|
||||
// border: none!important;
|
||||
outline: 1px solid #997e1f!important;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.skillSheet{
|
||||
height: 100%;
|
||||
padding: 0 27px 30px 27px;
|
||||
padding-bottom: 30px;
|
||||
.css-15l7r2q{
|
||||
gap: 27px!important;
|
||||
}
|
||||
.acss-134lb3j{
|
||||
--rows: 10!important;
|
||||
--max-item-width: 298px!important;
|
||||
}
|
||||
.skillSheetSpotlightCard{
|
||||
.acss-1iddemf{
|
||||
/* background: rgba(45, 45, 45, 0.75); */
|
||||
background: #121212;
|
||||
}
|
||||
.hover-card::before{
|
||||
background: radial-gradient(800px circle at var(--mouse-x) var(--mouse-y), rgba(255, 255, 255, 0.06), transparent 40%);
|
||||
}
|
||||
.hover-card::after{
|
||||
background: radial-gradient(600px circle at var(--mouse-x) var(--mouse-y), rgba(255, 255, 255, 0.4), transparent 40%);
|
||||
}
|
||||
.acss-13wwyd1{
|
||||
background: #111111;
|
||||
}
|
||||
}
|
||||
.selectNpcFlexbox{
|
||||
min-height: 127px;
|
||||
display: block;
|
||||
/* padding: 14px; */
|
||||
>div:nth-of-type(2){
|
||||
display: flex;
|
||||
align-items: self-start;
|
||||
padding: 14px 14px 0 14px;
|
||||
>div{
|
||||
margin-left: 15px;
|
||||
>p{
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
>div{
|
||||
display: flex;
|
||||
>div{
|
||||
margin-top: 4px;
|
||||
padding: 0 6px;
|
||||
height: 17px;
|
||||
background: rgba(255, 213, 76, 0.15);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 7px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>p{
|
||||
padding: 0 14px;
|
||||
line-height: 21px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
.npcInfoItemBg{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 64px;
|
||||
margin-bottom: -56px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
-webkit-mask-image: linear-gradient(to bottom, #fff, transparent);
|
||||
mask-image: linear-gradient(to bottom, #fff, transparent);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
>span{
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
font-size: 18px;
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
border: 1px solid transparent;
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
-webkit-filter: blur(50px) saturate(2);
|
||||
filter: blur(50px) saturate(2);
|
||||
>span{
|
||||
transform: scale(1);
|
||||
font-size: 140px;
|
||||
font-weight: 700;
|
||||
line-height: 1 !important;
|
||||
color: #fff;
|
||||
>div{
|
||||
height: 130px;
|
||||
width: 130px;
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.del{
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ export type FlowType = {
|
||||
date_created?: string;
|
||||
updated_at?: string;
|
||||
last_tested_version?: string;
|
||||
avatar_img?: string;
|
||||
avatar_color?: string;
|
||||
};
|
||||
export type NodeType = {
|
||||
id: string;
|
||||
|
||||
@@ -76,6 +76,7 @@ export function useTable<T extends object>(param, apiFun) {
|
||||
apiFun({ ...page, ...paramRef.current }).then(res => {
|
||||
if (requestId !== requestIdRef.current) return
|
||||
if (!("total" in res)) return console.error('该接口不支持分页,无法正常使用 useTable')
|
||||
// res.data.unshift({type:0})
|
||||
setData(res.data);
|
||||
setTotal(res.total);
|
||||
setLoading(false);
|
||||
|
||||