Merge branch 'chat_and_cache' into 186-endpoints-for-node-validation-and-debugging
This commit is contained in:
commit
5dbca85512
55 changed files with 1736 additions and 606 deletions
|
|
@ -1,5 +0,0 @@
|
|||
FROM node:14-alpine
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#! /bin/bash
|
||||
|
||||
# Read the contents of the JSON file
|
||||
json=$(cat package.json)
|
||||
|
||||
# Extract the value of the "version" field using jq
|
||||
VERSION=$(echo "$json" | jq -r '.version')
|
||||
|
||||
docker build -t logspace/frontend_build -f build.Dockerfile .
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_frontend:$VERSION .
|
||||
docker push ibiscp/langflow_frontend:$VERSION
|
||||
37
src/frontend/package-lock.json
generated
37
src/frontend/package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"@heroicons/react": "^2.0.15",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
|
@ -24,13 +25,14 @@
|
|||
"ace-builds": "^1.16.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"axios": "^1.3.2",
|
||||
"base64-js": "^1.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.2",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
|
|
@ -3931,6 +3933,14 @@
|
|||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/line-clamp": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz",
|
||||
"integrity": "sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz",
|
||||
|
|
@ -5854,6 +5864,25 @@
|
|||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/batch": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||
|
|
@ -15032,9 +15061,9 @@
|
|||
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz",
|
||||
"integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
|
||||
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"@heroicons/react": "^2.0.15",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
|
@ -19,13 +20,14 @@
|
|||
"ace-builds": "^1.16.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"axios": "^1.3.2",
|
||||
"base64-js": "^1.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.2",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
|
|
@ -60,5 +62,5 @@
|
|||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"proxy": "http://backend:7860"
|
||||
"proxy": "http://127.0.0.1:5003"
|
||||
}
|
||||
40
src/frontend/src/assets/Gooey Ring-5s-271px.svg
Normal file
40
src/frontend/src/assets/Gooey Ring-5s-271px.svg
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="271px" height="271px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<defs>
|
||||
<filter id="ldio-978hsxudfzl-filter" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="3.6"></feGaussianBlur>
|
||||
<feComponentTransfer result="cutoff">
|
||||
<feFuncA type="table" tableValues="0 0 0 0 0 0 1 1 1 1 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#ldio-978hsxudfzl-filter)"><g transform="translate(50 50)">
|
||||
<g>
|
||||
<circle cx="8" cy="0" r="5" fill="#2edbb5">
|
||||
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="5s" repeatCount="indefinite" begin="-0.2s"></animate>
|
||||
</circle>
|
||||
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="5s" repeatCount="indefinite" begin="0s"></animateTransform>
|
||||
</g>
|
||||
</g><g transform="translate(50 50)">
|
||||
<g>
|
||||
<circle cx="8" cy="0" r="5" fill="#1d99ff">
|
||||
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="2.5s" repeatCount="indefinite" begin="-0.15000000000000002s"></animate>
|
||||
</circle>
|
||||
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="2.5s" repeatCount="indefinite" begin="-0.05s"></animateTransform>
|
||||
</g>
|
||||
</g><g transform="translate(50 50)">
|
||||
<g>
|
||||
<circle cx="8" cy="0" r="5" fill="#4f41ff">
|
||||
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animate>
|
||||
</circle>
|
||||
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animateTransform>
|
||||
</g>
|
||||
</g><g transform="translate(50 50)">
|
||||
<g>
|
||||
<circle cx="8" cy="0" r="5" fill="#8400ff">
|
||||
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.25s" repeatCount="indefinite" begin="-0.05s"></animate>
|
||||
</circle>
|
||||
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.25s" repeatCount="indefinite" begin="-0.15000000000000002s"></animateTransform>
|
||||
</g>
|
||||
</g></g>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1,6 +1,7 @@
|
|||
import { ReactElement } from "react";
|
||||
import { LightTooltip } from "../LightTooltipComponent";
|
||||
import { TooltipComponentType } from "../../types/components";
|
||||
|
||||
export default function Tooltip({ children, title }:{children:ReactElement,title:string}) {
|
||||
return <LightTooltip title={title} arrow>{children}</LightTooltip>;
|
||||
export default function Tooltip({ children, title,placement }:TooltipComponentType) {
|
||||
return <LightTooltip placement={placement} title={title} arrow>{children}</LightTooltip>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { Bars3CenterLeftIcon, ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
|
||||
import { nodeColors } from "../../../utils";
|
||||
import { PopUpContext } from "../../../contexts/popUpContext";
|
||||
import { useContext } from "react";
|
||||
import ChatModal from "../../../modals/chatModal";
|
||||
|
||||
export default function ChatTrigger({open, setOpen,flow}){
|
||||
const {openPopUp} = useContext(PopUpContext)
|
||||
return(<Transition
|
||||
show={!open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="absolute bottom-2 right-3">
|
||||
<div style={{backgroundColor:nodeColors['chat']}} className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full dark:bg-gray-800 dark:border-gray-600 dark:text-white">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<ChatBubbleBottomCenterTextIcon
|
||||
className="h-6 w-6 mt-1"
|
||||
style={{ color: "white" }}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>)
|
||||
}
|
||||
|
|
@ -1,291 +1,32 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import {
|
||||
Bars3CenterLeftIcon,
|
||||
LockClosedIcon,
|
||||
PaperAirplaneIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
MouseEventHandler,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { sendAll } from "../../controllers/API";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { classNames, nodeColors, snakeToNormalCase } from "../../utils";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { ChatType } from "../../types/chat";
|
||||
import ChatMessage from "./chatMessage";
|
||||
import { NodeType } from "../../types/flow";
|
||||
|
||||
import { ChatMessageType, ChatType } from "../../types/chat";
|
||||
import ChatTrigger from "./chatTrigger";
|
||||
import ChatModal from "../../modals/chatModal";
|
||||
|
||||
const _ = require("lodash");
|
||||
|
||||
export default function Chat({ flow, reactFlowInstance }: ChatType) {
|
||||
const { updateFlow, lockChat, setLockChat, flows, tabIndex } =
|
||||
useContext(TabsContext);
|
||||
const [saveChat, setSaveChat] = useState(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const [chatHistory, setChatHistory] = useState(flow.chat);
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const addChatHistory = (
|
||||
message: string,
|
||||
isSend: boolean,
|
||||
thought?: string
|
||||
) => {
|
||||
let tabsChange = false;
|
||||
setChatHistory((old) => {
|
||||
let newChat = _.cloneDeep(old);
|
||||
if (JSON.stringify(flow.chat) !== JSON.stringify(old)) {
|
||||
tabsChange = true;
|
||||
return old;
|
||||
}
|
||||
if (thought) {
|
||||
newChat.push({ message, isSend, thought });
|
||||
} else {
|
||||
newChat.push({ message, isSend });
|
||||
}
|
||||
return newChat;
|
||||
});
|
||||
if (tabsChange) {
|
||||
if (thought) {
|
||||
updateFlow({
|
||||
..._.cloneDeep(flow),
|
||||
chat: [...flow.chat, { isSend, message, thought }],
|
||||
});
|
||||
} else {
|
||||
updateFlow({
|
||||
..._.cloneDeep(flow),
|
||||
chat: [...flow.chat, { isSend, message }],
|
||||
});
|
||||
}
|
||||
}
|
||||
setSaveChat((chat) => !chat);
|
||||
};
|
||||
export default function Chat({ flow }: ChatType) {
|
||||
const [open, setOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
updateFlow({ ..._.cloneDeep(flow), chat: chatHistory });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [saveChat]);
|
||||
useEffect(() => {
|
||||
setChatHistory(flow.chat);
|
||||
}, [flow]);
|
||||
useEffect(() => {
|
||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||
}, [chatHistory]);
|
||||
|
||||
function validateNode(n: NodeType): Array<string> {
|
||||
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
||||
setNoticeData({
|
||||
title:
|
||||
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
node: { template },
|
||||
} = n.data;
|
||||
|
||||
return Object.keys(template).reduce(
|
||||
(errors: Array<string>, t) =>
|
||||
errors.concat(
|
||||
(template[t].required && template[t].show) &&
|
||||
(!template[t].value || template[t].value === "") &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.some(
|
||||
(e) =>
|
||||
e.targetHandle.split("|")[1] === t &&
|
||||
e.targetHandle.split("|")[2] === n.id
|
||||
)
|
||||
? [
|
||||
`${type} is missing ${
|
||||
template.display_name
|
||||
? template.display_name
|
||||
: snakeToNormalCase(template[t].name)
|
||||
}.`,
|
||||
]
|
||||
: []
|
||||
),
|
||||
[] as string[]
|
||||
);
|
||||
}
|
||||
|
||||
function validateNodes() {
|
||||
return reactFlowInstance
|
||||
.getNodes()
|
||||
.flatMap((n: NodeType) => validateNode(n));
|
||||
}
|
||||
|
||||
const ref = useRef(null);
|
||||
|
||||
function sendMessage() {
|
||||
if (chatValue !== "") {
|
||||
let nodeValidationErrors = validateNodes();
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
setLockChat(true);
|
||||
let message = chatValue;
|
||||
setChatValue("");
|
||||
addChatHistory(message, true);
|
||||
|
||||
sendAll({
|
||||
...reactFlowInstance.toObject(),
|
||||
message,
|
||||
chatHistory,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
})
|
||||
.then((r) => {
|
||||
addChatHistory(r.data.result, false, r.data.thought);
|
||||
setLockChat(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: error.message ?? "Unknown Error",
|
||||
list: [error.response.data.detail],
|
||||
});
|
||||
setLockChat(false);
|
||||
let lastMessage;
|
||||
setChatHistory((chatHistory) => {
|
||||
let newChat = chatHistory;
|
||||
|
||||
lastMessage = newChat.pop().message;
|
||||
return newChat;
|
||||
});
|
||||
setChatValue(lastMessage);
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed some required information:",
|
||||
list: nodeValidationErrors,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error sending message",
|
||||
list: ["The message cannot be empty."],
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearChat() {
|
||||
setChatHistory([]);
|
||||
updateFlow({ ..._.cloneDeep(flow), chat: [] });
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "K" && event.shiftKey && (event.metaKey||event.ctrlKey)) {
|
||||
setOpen(oldState=>!oldState);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
show={open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="w-[340px] absolute bottom-0 right-1">
|
||||
<div className="border dark:border-gray-700 h-full rounded-xl rounded-b-none bg-white dark:bg-gray-800 shadow">
|
||||
<div
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
className="flex justify-between cursor-pointer items-center px-5 py-2 border-b dark:border-b-gray-700"
|
||||
>
|
||||
<div className="flex gap-3 text-lg dark:text-white font-medium items-center">
|
||||
<Bars3CenterLeftIcon
|
||||
className="h-5 w-5 mt-1"
|
||||
style={{ color: nodeColors["chat"] }}
|
||||
/>
|
||||
Chat
|
||||
</div>
|
||||
<button
|
||||
className="hover:text-blue-500 dark:text-white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
clearChat();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-full h-[400px] flex gap-3 mb-auto overflow-y-auto scrollbar-hide flex-col bg-gray-50 dark:bg-gray-900 p-3 py-5">
|
||||
{chatHistory.map((c, i) => (
|
||||
<ChatMessage chat={c} key={i} />
|
||||
))}
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex items-center justify-between p-3">
|
||||
<div className="relative w-full mt-1 rounded-md shadow-sm">
|
||||
<input
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !lockChat) {
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
disabled={lockChat}
|
||||
value={lockChat ? "Thinking..." : chatValue}
|
||||
onChange={(e) => {
|
||||
setChatValue(e.target.value);
|
||||
}}
|
||||
className={classNames(
|
||||
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
|
||||
"form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
|
||||
)}
|
||||
placeholder={"Send a message..."}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||
{lockChat ? (
|
||||
<LockClosedIcon
|
||||
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<PaperAirplaneIcon
|
||||
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition
|
||||
show={!open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="absolute bottom-0 right-1">
|
||||
<div className="border flex justify-center align-center py-1 px-3 rounded-xl rounded-b-none bg-white dark:bg-gray-800 dark:border-gray-600 dark:text-white shadow">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<Bars3CenterLeftIcon
|
||||
className="h-6 w-6 mt-1"
|
||||
style={{ color: nodeColors["chat"] }}
|
||||
/>
|
||||
Chat
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||
<ChatTrigger open={open} setOpen={setOpen} flow={flow} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default function LoadingComponent({remSize}:LoadingComponentProps){
|
|||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
<br></br>
|
||||
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,12 +13,10 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
|||
<DarkProvider>
|
||||
<LocationProvider>
|
||||
<AlertProvider>
|
||||
<TabsProvider>
|
||||
<PopUpProvider>
|
||||
<TabsProvider>
|
||||
<TypesProvider>
|
||||
{children}
|
||||
<PopUpProvider>{children}</PopUpProvider>
|
||||
</TypesProvider>
|
||||
</PopUpProvider>
|
||||
</TabsProvider>
|
||||
</AlertProvider>
|
||||
</LocationProvider>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import { createContext, useEffect, useState, useRef, ReactNode, useContext } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
ReactNode,
|
||||
useContext,
|
||||
} from "react";
|
||||
import { FlowType } from "../types/flow";
|
||||
import { TabsContextType } from "../types/tabs";
|
||||
import { normalCaseToSnakeCase } from "../utils";
|
||||
import { alertContext } from "./alertContext";
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const TabsContextInitialValue: TabsContextType = {
|
||||
save:()=>{},
|
||||
save: () => {},
|
||||
tabIndex: 0,
|
||||
setTabIndex: (index: number) => {},
|
||||
flows: [],
|
||||
|
|
@ -13,11 +21,9 @@ const TabsContextInitialValue: TabsContextType = {
|
|||
addFlow: (flowData?: any) => {},
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => 0,
|
||||
downloadFlow: (flow:FlowType) => {},
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
uploadFlow: () => {},
|
||||
lockChat: false,
|
||||
setLockChat:(prevState:boolean)=>{},
|
||||
hardReset:()=>{},
|
||||
hardReset: () => {},
|
||||
};
|
||||
|
||||
export const TabsContext = createContext<TabsContextType>(
|
||||
|
|
@ -25,31 +31,28 @@ export const TabsContext = createContext<TabsContextType>(
|
|||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const {setNoticeData} = useContext(alertContext)
|
||||
const { setNoticeData } = useContext(alertContext);
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState(0);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const [id, setId] = useState("");
|
||||
|
||||
const newNodeId = useRef(0);
|
||||
function incrementNodeId() {
|
||||
newNodeId.current = newNodeId.current + 1;
|
||||
return newNodeId.current;
|
||||
}
|
||||
function save(){
|
||||
function save() {
|
||||
if (flows.length !== 0)
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
||||
);
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
//save tabs locally
|
||||
save()
|
||||
save();
|
||||
}, [flows, id, tabIndex, newNodeId]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//get tabs locally saved
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
|
|
@ -61,15 +64,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
newNodeId.current = cookieObject.nodeId;
|
||||
}
|
||||
}, []);
|
||||
function hardReset(){
|
||||
newNodeId.current=0;
|
||||
setTabIndex(0);setFlows([]);setId(0);
|
||||
function hardReset() {
|
||||
newNodeId.current = 0;
|
||||
setTabIndex(0);
|
||||
setFlows([]);
|
||||
setId(uuidv4());
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the current flow as a JSON file
|
||||
*/
|
||||
function downloadFlow(flow:FlowType) {
|
||||
function downloadFlow(flow: FlowType) {
|
||||
// create a data URI with the current flow data
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flow)
|
||||
|
|
@ -82,7 +87,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// simulate a click on the link element to trigger the download
|
||||
link.click();
|
||||
setNoticeData({title:"Warning: Critical data,JSON file may including API keys."})
|
||||
setNoticeData({
|
||||
title: "Warning: Critical data,JSON file may including API keys.",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,19 +146,18 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
function addFlow(flow?: FlowType) {
|
||||
// Get data from the flow or set it to null if there's no flow provided.
|
||||
const data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description?flow.description:""
|
||||
const description = flow?.description ? flow.description : "";
|
||||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
let newFlow: FlowType = {
|
||||
description,
|
||||
name: "New Flow",
|
||||
name: flow?.name ?? "New Flow",
|
||||
id: id.toString(),
|
||||
data,
|
||||
chat: flow ? flow.chat : [],
|
||||
};
|
||||
|
||||
// Increment the ID counter.
|
||||
setId((old) => old + 1);
|
||||
setId(uuidv4());
|
||||
|
||||
// Add the new flow to the list of flows.
|
||||
setFlows((prevState) => {
|
||||
|
|
@ -171,10 +177,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
if (index !== -1) {
|
||||
newFlows[index].description = newFlow.description??""
|
||||
newFlows[index].description = newFlow.description ?? "";
|
||||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
newFlows[index].chat = newFlow.chat;
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
|
|
@ -185,8 +190,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
value={{
|
||||
save,
|
||||
hardReset,
|
||||
lockChat,
|
||||
setLockChat,
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { PromptTypeAPI, errorsTypeAPI } from './../../types/api/index';
|
||||
import { APIObjectType, sendAllProps } from '../../types/api/index';
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { FlowType } from '../../types/flow';
|
||||
|
||||
export async function getAll():Promise<AxiosResponse<APIObjectType>> {
|
||||
return await axios.get(`/all`);
|
||||
|
|
@ -18,4 +19,22 @@ export async function checkCode(code:string):Promise<AxiosResponse<errorsTypeAPI
|
|||
export async function checkPrompt(template:string):Promise<AxiosResponse<PromptTypeAPI>>{
|
||||
|
||||
return await axios.post('/validate/prompt',{template})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getExamples(): Promise<FlowType[]> {
|
||||
const url = 'https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples';
|
||||
const response = await axios.get(url);
|
||||
|
||||
const jsonFiles = response.data.filter((file: any) => {
|
||||
return file.name.endsWith('.json');
|
||||
});
|
||||
|
||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||
const contentResponse = await axios.get(file.download_url);
|
||||
return contentResponse.data;
|
||||
});
|
||||
|
||||
const contents = await Promise.all(contentsPromises);
|
||||
|
||||
return contents;
|
||||
}
|
||||
55
src/frontend/src/modals/chatModal/chatInput/index.tsx
Normal file
55
src/frontend/src/modals/chatModal/chatInput/index.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
||||
import { classNames } from "../../../utils";
|
||||
import { useRef } from "react";
|
||||
|
||||
export default function ChatInput({
|
||||
lockChat,
|
||||
chatValue,
|
||||
sendMessage,
|
||||
setChatValue,
|
||||
}: {
|
||||
lockChat: boolean;
|
||||
chatValue: string;
|
||||
sendMessage: Function;
|
||||
setChatValue: Function;
|
||||
}) {
|
||||
const inputRef = useRef(null);
|
||||
return (
|
||||
<>
|
||||
<textarea
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !lockChat && !event.shiftKey) {
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
disabled={lockChat}
|
||||
style={{resize: "none" }}
|
||||
value={lockChat ? "Thinking..." : chatValue}
|
||||
onChange={(e) => {
|
||||
setChatValue(e.target.value);
|
||||
}}
|
||||
className={classNames(
|
||||
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
|
||||
"form-input block w-full custom-scroll h-10 rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
|
||||
)}
|
||||
placeholder={"Send a message..."}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||
{lockChat ? (
|
||||
<LockClosedIcon
|
||||
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<PaperAirplaneIcon
|
||||
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
82
src/frontend/src/modals/chatModal/chatMessage/index.tsx
Normal file
82
src/frontend/src/modals/chatModal/chatMessage/index.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { classNames } from "../../../utils";
|
||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||
import { UserIcon } from "@heroicons/react/24/solid";
|
||||
import FileCard from "../fileComponent";
|
||||
var Convert = require("ansi-to-html");
|
||||
var convert = new Convert({ newline: true });
|
||||
|
||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||
const [hidden, setHidden] = useState(true);
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"w-full py-2 pl-2 flex",
|
||||
chat.isSend ? "bg-white" : "bg-gray-200"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"rounded-full w-8 h-8 flex items-center my-3 justify-center",
|
||||
chat.isSend ? "bg-gray-900" : "bg-gray-200"
|
||||
)}
|
||||
>
|
||||
{!chat.isSend && <img className="scale-150" src={AiIcon} />}
|
||||
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-200" />}
|
||||
</div>
|
||||
{!chat.isSend ? (
|
||||
<div className="w-full text-start flex items-center">
|
||||
<div className=" relative text-start inline-block text-gray-600 text-sm font-normal">
|
||||
{hidden && chat.thought && chat.thought !== "" && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute -top-1 -left-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className=" text-start inline-block rounded-md h-full border border-gray-300
|
||||
bg-gray-100 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: convert.toHtml(chat.thought),
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||
<span>
|
||||
{chat.message}
|
||||
{chat.files && (
|
||||
<div className="my-2 w-full">
|
||||
{chat.files.map((file, index) => {
|
||||
return (
|
||||
<div key={index} className="my-2 w-full">
|
||||
<FileCard
|
||||
fileName={"Generated File"}
|
||||
fileType={file.data_type}
|
||||
content={file.data}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full flex items-center">
|
||||
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white dark:bg-gray-700">
|
||||
{chat.message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/frontend/src/modals/chatModal/fileComponent/index.tsx
Normal file
36
src/frontend/src/modals/chatModal/fileComponent/index.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||
import * as base64js from 'base64-js';
|
||||
|
||||
export default function FileCard({ fileName, content, fileType }) {
|
||||
const handleDownload = () => {
|
||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||
const blob = new Blob([byteArray], { type: 'application/octet-stream' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName+".png";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="bg-gray-100 shadow rounded w-1/2 text-gray-700 hover:drop-shadow-lg px-2 py-2 flex justify-between items-center border border-gray-300"
|
||||
>
|
||||
<div className="flex gap-2 text-current items-center w-full mr-2">
|
||||
{" "}
|
||||
<DocumentIcon className="w-8 h-8" />
|
||||
<div className="flex flex-col items-start">
|
||||
{" "}
|
||||
<div className="truncate text-sm text-current">{fileName}</div>
|
||||
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
||||
|
||||
</div>
|
||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
330
src/frontend/src/modals/chatModal/index.tsx
Normal file
330
src/frontend/src/modals/chatModal/index.tsx
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import {
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
LockClosedIcon,
|
||||
PaperAirplaneIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { FlowType, NodeType } from "../../types/flow";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { classNames, snakeToNormalCase } from "../../utils";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import ChatMessage from "./chatMessage";
|
||||
import { FaEraser } from "react-icons/fa";
|
||||
import { sendAllProps } from "../../types/api";
|
||||
import { ChatMessageType, ChatType } from "../../types/chat";
|
||||
import ChatInput from "./chatInput";
|
||||
|
||||
const _ = require("lodash");
|
||||
|
||||
export default function ChatModal({
|
||||
flow,
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: Function;
|
||||
flow: FlowType;
|
||||
}) {
|
||||
const { updateFlow } = useContext(TabsContext);
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const addChatHistory = (
|
||||
message: string,
|
||||
isSend: boolean,
|
||||
thought?: string,
|
||||
files?: Array<any>
|
||||
) => {
|
||||
setChatHistory((old) => {
|
||||
let newChat = _.cloneDeep(old);
|
||||
if (files) {
|
||||
newChat.push({ message, isSend, files });
|
||||
} else if (thought) {
|
||||
newChat.push({ message, isSend, thought });
|
||||
} else {
|
||||
newChat.push({ message, isSend });
|
||||
}
|
||||
return newChat;
|
||||
});
|
||||
};
|
||||
|
||||
function connectWS() {
|
||||
const newWs = new WebSocket(`ws://127.0.0.1:5003/chat/${flow.id}`);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
};
|
||||
newWs.onmessage = (event) => {
|
||||
try {
|
||||
setLockChat(false);
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Received data:", data);
|
||||
//get chat history
|
||||
if (Array.isArray(data)) {
|
||||
console.log(data);
|
||||
|
||||
setChatHistory((_) => {
|
||||
let newChatHistory: ChatMessageType[] = [];
|
||||
data.forEach(
|
||||
(chatItem: {
|
||||
intermediate_steps?: "string";
|
||||
is_bot: boolean;
|
||||
message: string;
|
||||
type: string;
|
||||
files?: Array<any>;
|
||||
}) => {
|
||||
if (chatItem.message) {
|
||||
newChatHistory.push(
|
||||
chatItem.files
|
||||
? {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.message,
|
||||
thought: chatItem.intermediate_steps,
|
||||
files: chatItem.files,
|
||||
}
|
||||
: {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.message,
|
||||
thought: chatItem.intermediate_steps,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
return newChatHistory;
|
||||
});
|
||||
}
|
||||
if (data.type === "end") {
|
||||
if (data.files) {
|
||||
addChatHistory(
|
||||
data.message,
|
||||
false,
|
||||
data.intermediate_steps,
|
||||
data.files
|
||||
);
|
||||
} else {
|
||||
addChatHistory(data.message, false, data.intermediate_steps);
|
||||
}
|
||||
}
|
||||
if (data.type == "file") {
|
||||
console.log(data);
|
||||
}
|
||||
} catch (error) {
|
||||
if(event.data!=="Error: 1005"){
|
||||
setErrorData({ title: event.data });
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
newWs.onclose = (_) => {
|
||||
if (open) {
|
||||
setLockChat(false);
|
||||
setTimeout(() => {
|
||||
connectWS();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
setWs(newWs);
|
||||
|
||||
return newWs;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let newWs = connectWS();
|
||||
return () => {
|
||||
newWs.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function sendAll(data: sendAllProps) {
|
||||
if (ws) {
|
||||
ws.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||
}, [chatHistory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ws && ws.readyState === ws.CLOSED) {
|
||||
setLockChat(false);
|
||||
}
|
||||
}, [lockChat]);
|
||||
|
||||
function validateNode(n: NodeType): Array<string> {
|
||||
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
||||
setNoticeData({
|
||||
title:
|
||||
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
node: { template },
|
||||
} = n.data;
|
||||
|
||||
return Object.keys(template).reduce(
|
||||
(errors: Array<string>, t) =>
|
||||
errors.concat(
|
||||
template[t].required &&
|
||||
template[t].show &&
|
||||
(!template[t].value || template[t].value === "") &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.some(
|
||||
(e) =>
|
||||
e.targetHandle.split("|")[1] === t &&
|
||||
e.targetHandle.split("|")[2] === n.id
|
||||
)
|
||||
? [
|
||||
`${type} is missing ${
|
||||
template.display_name
|
||||
? template.display_name
|
||||
: snakeToNormalCase(template[t].name)
|
||||
}.`,
|
||||
]
|
||||
: []
|
||||
),
|
||||
[] as string[]
|
||||
);
|
||||
}
|
||||
|
||||
function validateNodes() {
|
||||
return reactFlowInstance
|
||||
.getNodes()
|
||||
.flatMap((n: NodeType) => validateNode(n));
|
||||
}
|
||||
|
||||
const ref = useRef(null);
|
||||
|
||||
function sendMessage() {
|
||||
if (chatValue !== "") {
|
||||
let nodeValidationErrors = validateNodes();
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
setLockChat(true);
|
||||
let message = chatValue;
|
||||
setChatValue("");
|
||||
addChatHistory(message, true);
|
||||
|
||||
sendAll({
|
||||
...reactFlowInstance.toObject(),
|
||||
message,
|
||||
chatHistory,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed some required information:",
|
||||
list: nodeValidationErrors,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error sending message",
|
||||
list: ["The message cannot be empty."],
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearChat() {
|
||||
setChatHistory([]);
|
||||
ws.send(JSON.stringify({ clear_history: true }));
|
||||
}
|
||||
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black backdrop-blur-sm dark:bg-gray-600 dark:bg-opacity-80 bg-opacity-80 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[690px]">
|
||||
<div className="relative w-full">
|
||||
<button
|
||||
onClick={() => clearChat()}
|
||||
className="absolute top-2 right-2 hover:text-red-500"
|
||||
>
|
||||
<FaEraser className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-full h-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center overflow-scroll scrollbar-hide">
|
||||
{chatHistory.length > 0 ? (
|
||||
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
|
||||
) : (
|
||||
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle ">
|
||||
<span>
|
||||
👋 <span className="text-gray-600 text-lg">LangFlow Chat</span>
|
||||
</span>
|
||||
<br />
|
||||
<div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200">
|
||||
<span className="text-base text-gray-500">
|
||||
Start a conversation and click the agent’s thoughts{" "}
|
||||
<span>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
|
||||
</span>{" "}
|
||||
to inspect the chaining process.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center justify-between p-3">
|
||||
<div className="relative w-full mt-1 rounded-md shadow-sm">
|
||||
<ChatInput
|
||||
chatValue={chatValue}
|
||||
lockChat={lockChat}
|
||||
sendMessage={sendMessage}
|
||||
setChatValue={setChatValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/solid";
|
||||
import { classNames } from "../../../utils";
|
||||
import Tooltip from "../../../components/TooltipComponent";
|
||||
|
||||
export default function ButtonBox({
|
||||
onClick,
|
||||
|
|
@ -9,7 +10,8 @@ export default function ButtonBox({
|
|||
icon,
|
||||
bgColor,
|
||||
textColor,
|
||||
deactivate
|
||||
deactivate,
|
||||
size,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
title: string;
|
||||
|
|
@ -17,31 +19,103 @@ export default function ButtonBox({
|
|||
icon: ReactNode;
|
||||
bgColor: string;
|
||||
textColor: string;
|
||||
deactivate?:boolean;
|
||||
deactivate?: boolean;
|
||||
size: "small" | "medium" | "big";
|
||||
}) {
|
||||
let bigCircle: string;
|
||||
let smallCircle: string;
|
||||
let titleFontSize: string;
|
||||
let descriptionFontSize: string;
|
||||
let padding: string;
|
||||
let marginTop: string;
|
||||
let height: string;
|
||||
let widht: string;
|
||||
switch (size) {
|
||||
case "small":
|
||||
bigCircle = "h-12 w-12";
|
||||
smallCircle = "h-8 w-8";
|
||||
titleFontSize = "text-sm";
|
||||
descriptionFontSize = "text-xs";
|
||||
padding = "p-2";
|
||||
marginTop = "mt-2";
|
||||
height = "h-36";
|
||||
widht = "w-32";
|
||||
break;
|
||||
case "medium":
|
||||
bigCircle = "h-16 w-16";
|
||||
smallCircle = "h-12 w-12";
|
||||
titleFontSize = "text-base";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-4";
|
||||
marginTop = "mt-3";
|
||||
height = "h-44";
|
||||
widht = "w-36";
|
||||
break;
|
||||
case "big":
|
||||
bigCircle = "h-20 w-20";
|
||||
smallCircle = "h-16 w-16";
|
||||
titleFontSize = "text-lg";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-8";
|
||||
marginTop = "mt-6";
|
||||
height = "h-56";
|
||||
widht = "w-44";
|
||||
break;
|
||||
default:
|
||||
bigCircle = "h-20 w-20";
|
||||
smallCircle = "h-16 w-16";
|
||||
titleFontSize = "text-lg";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-8";
|
||||
marginTop = "mt-6";
|
||||
height = "h-56";
|
||||
widht = "w-44";
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<button disabled={deactivate} onClick={onClick}>
|
||||
<div
|
||||
className={classNames(
|
||||
"col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg text-center shadow border border-gray-300 hover:shadow-lg transform hover:scale-105",
|
||||
bgColor
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-1 flex-col p-8">
|
||||
<div className="mx-auto flex items-center justify-center h-20 w-20 bg-white/30 rounded-full">
|
||||
<div className="mx-auto flex items-center justify-center h-16 w-16 bg-white rounded-full">
|
||||
<div className={textColor}>
|
||||
{icon}
|
||||
</div>
|
||||
<Tooltip title={description} placement="bottom">
|
||||
<div
|
||||
className={classNames(
|
||||
"col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg text-center shadow border border-gray-300 hover:shadow-lg transform hover:scale-105",
|
||||
bgColor,
|
||||
height,
|
||||
widht
|
||||
)}
|
||||
>
|
||||
<div className={`flex flex-1 flex-col ${padding}`}>
|
||||
<div
|
||||
className={`mx-auto flex items-center justify-center ${bigCircle} bg-white/30 rounded-full`}
|
||||
>
|
||||
<div
|
||||
className={`mx-auto flex items-center justify-center ${smallCircle} bg-white rounded-full`}
|
||||
>
|
||||
<div className={textColor}>{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3
|
||||
className={classNames(
|
||||
"font-semibold text-white",
|
||||
titleFontSize,
|
||||
marginTop
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<div className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">{title}</dt>
|
||||
{/* <dd
|
||||
className={classNames(
|
||||
"text-gray-100 line-clamp-2",
|
||||
descriptionFontSize
|
||||
)}
|
||||
>
|
||||
{deactivate ? "Coming soon" : description}
|
||||
</dd> */}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mt-6 text-lg font-semibold text-white">{title}</h3>
|
||||
<div className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">{title}</dt>
|
||||
<dd className="text-sm text-gray-100">{deactivate? "Coming soon":description}</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,30 @@ import {
|
|||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ComputerDesktopIcon,
|
||||
ComputerDesktopIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ArrowLeftIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import ButtonBox from "./buttonBox";
|
||||
import { getExamples } from "../../controllers/API";
|
||||
import { error } from "console";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import LoadingComponent from "../../components/loadingComponent";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { classNames } from "../../utils";
|
||||
|
||||
export default function ImportModal() {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
const {uploadFlow} = useContext(TabsContext)
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [loadingExamples, setLoadingExamples] = useState(false);
|
||||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
|
|
@ -24,6 +35,22 @@ export default function ImportModal() {
|
|||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function handleExamples() {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
setLoadingExamples(false);
|
||||
setExamples(result);
|
||||
})
|
||||
.catch((error) =>
|
||||
setErrorData({
|
||||
title: "there was an error loading examples, please try again",
|
||||
list: [error.message],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
|
|
@ -68,6 +95,41 @@ export default function ImportModal() {
|
|||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{showExamples && (
|
||||
<>
|
||||
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setShowExamples(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="z-50 absolute bottom-2 left-0 hidden pt-4 pl-2 sm:block">
|
||||
<a
|
||||
href="https://github.com/logspace-ai/langflow_examples"
|
||||
target="_blank"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
viewBox="0 0 98 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="#24292f"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
|
|
@ -81,36 +143,81 @@ export default function ImportModal() {
|
|||
as="h3"
|
||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||
>
|
||||
Import from
|
||||
{showExamples ?"Select an example":"Import from"}
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||
<div className="flex h-full w-full justify-evenly items-center">
|
||||
<ButtonBox
|
||||
deactivate
|
||||
bgColor="bg-slate-400"
|
||||
description="Prebuilt Examples"
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => console.log("sdsds")}
|
||||
textColor="text-slate-400"
|
||||
title="Examples"
|
||||
></ButtonBox>
|
||||
<ButtonBox
|
||||
bgColor="bg-blue-500"
|
||||
description="Import from Local"
|
||||
icon={
|
||||
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {uploadFlow();setModalOpen(false)}}
|
||||
textColor="text-blue-500"
|
||||
title="Local file"
|
||||
></ButtonBox>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4",
|
||||
showExamples && !loadingExamples
|
||||
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
|
||||
: "flex flex-row justify-center items-center p-4"
|
||||
)}
|
||||
>
|
||||
{!showExamples && (
|
||||
<div className="flex h-full w-full justify-evenly items-center">
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-emerald-500"
|
||||
description="Prebuilt Examples"
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
setShowExamples(true);
|
||||
handleExamples();
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title="Examples"
|
||||
></ButtonBox>
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-blue-500"
|
||||
description="Import from Local"
|
||||
icon={
|
||||
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
uploadFlow();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-blue-500"
|
||||
title="Local file"
|
||||
></ButtonBox>
|
||||
</div>
|
||||
)}
|
||||
{showExamples && loadingExamples && (
|
||||
<div className="flex align-middle justify-center items-center">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
)}
|
||||
{showExamples &&
|
||||
!loadingExamples &&
|
||||
examples.map((example, index) => {
|
||||
return (
|
||||
<div id="index">
|
||||
{" "}
|
||||
<ButtonBox
|
||||
size="small"
|
||||
bgColor="bg-emerald-500"
|
||||
description={
|
||||
example.description ?? "Prebuilt Examples"
|
||||
}
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
addFlow(example);
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title={example.name}
|
||||
></ButtonBox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
|
|||
className="dark:text-white flex justify-between select-none truncate w-44 items-center px-4 my-1.5 border-x border-x-gray-300 dark:border-x-gray-600 -ml-px"
|
||||
onClick={onClick}
|
||||
>
|
||||
{flow.name}
|
||||
<span className="w-32 truncate text-left">{flow.name}</span>
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -60,18 +60,18 @@ export default function TabsManagerComponent() {
|
|||
/>
|
||||
)
|
||||
}
|
||||
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-400 hover:text-gray-500"
|
||||
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
|
||||
>
|
||||
Import <ArrowUpTrayIcon className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>openPopUp(<ExportModal/>)}
|
||||
className="flex items-center gap-1 mr-2 text-sm text-gray-400 hover:text-gray-500"
|
||||
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500"
|
||||
>
|
||||
Export <ArrowDownTrayIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-400 hover:text-gray-500 "
|
||||
className="text-gray-600 hover:text-gray-500 "
|
||||
onClick={() => {
|
||||
setDark(!dark);
|
||||
}}
|
||||
|
|
@ -83,7 +83,7 @@ export default function TabsManagerComponent() {
|
|||
)}
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-400 hover:text-gray-500 relative"
|
||||
className="text-gray-600 hover:text-gray-500 relative"
|
||||
onClick={(event: React.MouseEvent<HTMLElement>) => {
|
||||
setNotificationCenter(false);
|
||||
const top = (event.target as Element).getBoundingClientRect().top;
|
||||
|
|
|
|||
5
src/frontend/src/svg.d.ts
vendored
Normal file
5
src/frontend/src/svg.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
import { ReactFlowInstance } from 'reactflow';
|
||||
import { ReactFlowInstance } from "reactflow";
|
||||
import { FlowType } from "../flow";
|
||||
|
||||
export type ChatType = {flow:FlowType,reactFlowInstance:ReactFlowInstance}
|
||||
export type ChatMessageType = { message: string; isSend: boolean, thought?:string }
|
||||
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
|
||||
export type ChatMessageType = {
|
||||
message: string;
|
||||
isSend: boolean;
|
||||
thought?: string;
|
||||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,3 +65,17 @@ export type FloatComponentType = {
|
|||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
export type TooltipComponentType={children:ReactElement,title:string,placement?:
|
||||
| 'bottom-end'
|
||||
| 'bottom-start'
|
||||
| 'bottom'
|
||||
| 'left-end'
|
||||
| 'left-start'
|
||||
| 'left'
|
||||
| 'right-end'
|
||||
| 'right-start'
|
||||
| 'right'
|
||||
| 'top-end'
|
||||
| 'top-start'
|
||||
| 'top';}
|
||||
|
|
@ -6,7 +6,6 @@ export type FlowType = {
|
|||
name: string;
|
||||
id: string;
|
||||
data: ReactFlowJsonObject;
|
||||
chat: Array<ChatMessageType>;
|
||||
description:string;
|
||||
};
|
||||
export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,10 @@ export type TabsContextType = {
|
|||
setTabIndex: (index: number) => void;
|
||||
flows: Array<FlowType>;
|
||||
removeFlow: (id: string) => void;
|
||||
addFlow: (flowData?: any) => void;
|
||||
addFlow: (flowData?: FlowType) => void;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => number;
|
||||
downloadFlow: (flow:FlowType) => void;
|
||||
uploadFlow: () => void;
|
||||
lockChat:boolean;
|
||||
setLockChat:(prevState:boolean)=>void;
|
||||
hardReset:()=>void;
|
||||
};
|
||||
|
|
@ -78,7 +78,7 @@ export const nodeColors: {[char: string]: string} = {
|
|||
tools: "#FF3434",
|
||||
memories: "#F5B85A",
|
||||
advanced: "#000000",
|
||||
chat: "#454173",
|
||||
chat: "#198BF6",
|
||||
thought:"#272541",
|
||||
embeddings:"#42BAA7",
|
||||
documentloaders:"#7AAE42",
|
||||
|
|
|
|||
|
|
@ -52,11 +52,27 @@ module.exports = {
|
|||
margin: 0,
|
||||
},
|
||||
},
|
||||
".password": {
|
||||
"-webkit-text-security": "disc",
|
||||
"font-family": "text-security-disc",
|
||||
'.password':{
|
||||
"-webkit-text-security":"disc",
|
||||
"font-family": "text-security-disc"
|
||||
|
||||
},
|
||||
});
|
||||
}),
|
||||
'.custom-scroll':{
|
||||
'&::-webkit-scrollbar': {
|
||||
'width': '8px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
'backgroundColor': '#f1f1f1',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
'backgroundColor': '#ccc',
|
||||
'borderRadius': '999px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
'backgroundColor': '#bbb'
|
||||
}
|
||||
}
|
||||
})
|
||||
}),require('@tailwindcss/line-clamp')
|
||||
],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue