This commit is contained in:
zhangkai
2024-06-13 15:11:41 +08:00
parent 2734110636
commit fb39b66431
78 changed files with 2868 additions and 2800 deletions

BIN
src/.DS_Store vendored

Binary file not shown.

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/npc/npcIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/components/.DS_Store vendored Normal file

Binary file not shown.

View 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>({

View File

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

View File

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

View File

@@ -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() ?

View File

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

View File

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

View File

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

Binary file not shown.

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}`);
}
/**
*
* 修改支持库文件
*
*/

View File

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

View File

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

View File

@@ -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('')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>
};

View File

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

View 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>
};

View 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>
};

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

View 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>
};

View 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>
};

View 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>
};

View File

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

View File

@@ -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>NPCNPC </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"}}>NPCNPC上下线</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>
};

View File

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

View File

@@ -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={() => {
// 切换自定义工具 并 刷新

View File

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

View File

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

View 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
View 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()
// })
// })
}

View File

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

View File

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

View File

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