Merge branch 'chatUpdate' of https://github.com/logspace-ai/langflow into streaming

This commit is contained in:
Gabriel Almeida 2023-05-06 07:32:31 -03:00
commit b5a1fec33d
3 changed files with 355 additions and 341 deletions

View file

@ -5,7 +5,7 @@ import { PopUpContext } from "../../../contexts/popUpContext";
import { useContext } from "react"; import { useContext } from "react";
import ChatModal from "../../../modals/chatModal"; import ChatModal from "../../../modals/chatModal";
export default function ChatTrigger({open, setOpen,flow}){ export default function ChatTrigger({open, setOpen}){
const {openPopUp} = useContext(PopUpContext) const {openPopUp} = useContext(PopUpContext)
return(<Transition return(<Transition
show={!open} show={!open}

View file

@ -26,7 +26,7 @@ export default function Chat({ flow }: ChatType) {
return ( return (
<> <>
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} /> <ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
<ChatTrigger open={open} setOpen={setOpen} flow={flow} /> <ChatTrigger open={open} setOpen={setOpen} />
</> </>
); );
} }

View file

@ -1,13 +1,7 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
ChatBubbleOvalLeftEllipsisIcon,
LockClosedIcon,
PaperAirplaneIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useEffect, useRef, useState } from "react"; import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { FlowType, NodeType } from "../../types/flow"; import { FlowType, NodeType } from "../../types/flow";
import { TabsContext } from "../../contexts/tabsContext";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { toNormalCase } from "../../utils"; import { toNormalCase } from "../../utils";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
@ -20,356 +14,376 @@ import ChatInput from "./chatInput";
const _ = require("lodash"); const _ = require("lodash");
export default function ChatModal({ export default function ChatModal({
flow, flow,
open, open,
setOpen, setOpen,
}: { }: {
open: boolean; open: boolean;
setOpen: Function; setOpen: Function;
flow: FlowType; flow: FlowType;
}) { }) {
const [chatValue, setChatValue] = useState(""); const [chatValue, setChatValue] = useState("");
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]); const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext); const { setErrorData, setNoticeData } = useContext(alertContext);
const [ws, setWs] = useState<WebSocket | null>(null); const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false); const [lockChat, setLockChat] = useState(false);
const addChatHistory = ( const isOpen = useRef(open);
message: string,
isSend: boolean,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought });
} else if (thought) {
newChat.push({ message, isSend, thought });
} else {
newChat.push({ message, isSend });
}
return newChat;
});
};
function connectWS() { useEffect(() => {
console.log("conectou"); isOpen.current = open;
try { }, [open]);
const urlWs = var isStream = false;
process.env.NODE_ENV === "development"
? `ws://localhost:7860/chat/${flow.id}`
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${
window.location.host
}/chat/${flow.id}`;
const newWs = new WebSocket(urlWs); const addChatHistory = (
newWs.onopen = () => { message: string,
console.log("WebSocket connection established!"); isSend: boolean,
}; thought?: string,
newWs.onmessage = (event) => { files?: Array<any>
try { ) => {
setLockChat(false); setChatHistory((old) => {
const data = JSON.parse(event.data); let newChat = _.cloneDeep(old);
console.log("Received data:", data); if (files) {
//get chat history newChat.push({ message, isSend, files, thought });
if (Array.isArray(data)) { } else if (thought) {
console.log(data); newChat.push({ message, isSend, thought });
} else {
newChat.push({ message, isSend });
}
return newChat;
});
};
setChatHistory((_) => { //add proper type signature for function
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.close();
connectWS();
}
}
};
newWs.onclose = (_) => {
if (open) {
setLockChat(false);
setTimeout(() => {
connectWS();
}, 1000);
}
};
newWs.onerror = (ev) => {
console.log(ev, "error");
};
setWs(newWs);
return newWs; function updateLastMessage({str,thought}:{str?: string, thought?: string}) {
} catch { setChatHistory((old) => {
setErrorData({ let newChat = [...old];
title: "There was an error on web connection, please: ", if (str) {
list: [ newChat[newChat.length - 1].message =
"refresh the page", newChat[newChat.length - 1].message + str;
"use a new flow tab", }
"check if the backend is up", if(thought){
], newChat[newChat.length - 1].thought = thought
}); }
} return newChat;
} });
}
useEffect(() => { function handleOnClose(event: CloseEvent) {
if (ws && (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING)) { if (isOpen.current) {
let newWs = connectWS(); setLockChat(false);
return () => { setTimeout(() => {
console.log("trigger"); connectWS();
newWs.close(); }, 1000);
}; }
} }
}, [lockChat]);
useEffect(() => { function handleWsMessage(data: any) {
let newWs = connectWS(); if (Array.isArray(data)) {
return () => { //set chat history
console.log("trigger"); setChatHistory((_) => {
newWs.close(); 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 === "start") {
console.log("start");
addChatHistory("", false);
isStream = true;
}
if (data.type === "end") {
if(data.intermediate_steps){
updateLastMessage({thought:data.intermediate_steps});
}
setLockChat(false);
isStream = false;
// 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);
}
if (data.type === "stream" && isStream) {
updateLastMessage({str:data.message});
}
}
async function sendAll(data: sendAllProps) { function connectWS() {
try { try {
if (ws) { const urlWs =
ws.send(JSON.stringify(data)); process.env.NODE_ENV === "development"
} ? `ws://localhost:7860/chat/${flow.id}`
} catch (error) { : `${window.location.protocol === "https:" ? "wss" : "ws"}://${
setErrorData({ window.location.host
title: "There was an erro sending the message", }/chat/${flow.id}`;
list: [error.message],
});
setChatValue(data.message);
connectWS();
}
}
useEffect(() => { const newWs = new WebSocket(urlWs);
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); newWs.onopen = () => {
}, [chatHistory]); console.log("WebSocket connection established!");
};
console.log(flow.id);
newWs.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received data:", data);
handleWsMessage(data);
//get chat history
};
newWs.onclose = (event) => {
handleOnClose(event);
};
newWs.onerror = (ev) => {
console.log(ev, "error");
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
],
});
};
ws.current = newWs;
} catch {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
],
});
}
}
useEffect(() => { useEffect(() => {
if (ws && ws.readyState === ws.CLOSED) { connectWS();
setLockChat(false); return () => {
} console.log("unmount");
}, [lockChat]); console.log(ws);
if (ws) {
ws.current.close();
}
};
}, []);
function validateNode(n: NodeType): Array<string> { async function sendAll(data: sendAllProps) {
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) { try {
setNoticeData({ if (ws) {
title: ws.current.send(JSON.stringify(data));
"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!", }
}); } catch (error) {
return []; setErrorData({
} title: "There was an erro sending the message",
list: [error.message],
});
setChatValue(data.message);
connectWS();
}
}
const { useEffect(() => {
type, if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
node: { template }, }, [chatHistory]);
} = n.data;
return Object.keys(template).reduce( function validateNode(n: NodeType): Array<string> {
(errors: Array<string>, t) => if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
errors.concat( setNoticeData({
template[t].required && title:
template[t].show && "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!",
(!template[t].value || template[t].value === "") && });
!reactFlowInstance return [];
.getEdges() }
.some(
(e) =>
e.targetHandle.split("|")[1] === t &&
e.targetHandle.split("|")[2] === n.id
)
? [
`${type} is missing ${
template.display_name
? template.display_name
: toNormalCase(template[t].name)
}.`,
]
: []
),
[] as string[]
);
}
function validateNodes() { const {
return reactFlowInstance type,
.getNodes() node: { template },
.flatMap((n: NodeType) => validateNode(n)); } = n.data;
}
const ref = useRef(null); 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
: toNormalCase(template[t].name)
}.`,
]
: []
),
[] as string[]
);
}
function sendMessage() { function validateNodes() {
if (chatValue !== "") { return reactFlowInstance
let nodeValidationErrors = validateNodes(); .getNodes()
if (nodeValidationErrors.length === 0) { .flatMap((n: NodeType) => validateNode(n));
setLockChat(true); }
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
sendAll({ const ref = useRef(null);
...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 sendMessage() {
function setModalOpen(x: boolean) { if (chatValue !== "") {
setOpen(x); let nodeValidationErrors = validateNodes();
if (x === false) { if (nodeValidationErrors.length === 0) {
setTimeout(() => { setLockChat(true);
closePopUp(); let message = chatValue;
}, 300); setChatValue("");
} addChatHistory(message, true);
} sendAll({
return ( ...reactFlowInstance.toObject(),
<Transition.Root show={open} appear={true} as={Fragment}> message,
<Dialog chatHistory,
as="div" name: flow.name,
className="relative z-10" description: flow.description,
onClose={setModalOpen} });
initialFocus={ref} } else {
> setErrorData({
<Transition.Child title: "Oops! Looks like you missed some required information:",
as={Fragment} list: nodeValidationErrors,
enter="ease-out duration-300" });
enterFrom="opacity-0" }
enterTo="opacity-100" } else {
leave="ease-in duration-200" setErrorData({
leaveFrom="opacity-100" title: "Error sending message",
leaveTo="opacity-0" list: ["The message cannot be empty."],
> });
<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> }
function clearChat() {
setChatHistory([]);
ws.current.send(JSON.stringify({ clear_history: true }));
}
<div className="fixed inset-0 z-10 overflow-y-auto"> function setModalOpen(x: boolean) {
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> setOpen(x);
<Transition.Child }
as={Fragment} return (
enter="ease-out duration-300" <Transition.Root show={open} appear={open} as={Fragment}>
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" <Dialog
enterTo="opacity-100 translate-y-0 sm:scale-100" as="div"
leave="ease-in duration-200" className="relative z-10"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" onClose={setModalOpen}
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" initialFocus={ref}
> >
<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]"> <Transition.Child
<div className="relative w-full"> as={Fragment}
<button enter="ease-out duration-300"
onClick={() => clearChat()} enterFrom="opacity-0"
className="absolute top-2 right-2 hover:text-red-500" enterTo="opacity-100"
> leave="ease-in duration-200"
<FaEraser className="w-4 h-4" /> leaveFrom="opacity-100"
</button> leaveTo="opacity-0"
</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"> <div className="fixed inset-0 bg-black backdrop-blur-sm dark:bg-gray-600 dark:bg-opacity-80 bg-opacity-80 transition-opacity" />
{chatHistory.length > 0 ? ( </Transition.Child>
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
) : ( <div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle "> <div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<span> <Transition.Child
👋{" "} as={Fragment}
<span className="text-gray-600 text-lg"> enter="ease-out duration-300"
LangFlow Chat enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
</span> enterTo="opacity-100 translate-y-0 sm:scale-100"
</span> leave="ease-in duration-200"
<br /> leaveFrom="opacity-100 translate-y-0 sm:scale-100"
<div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200"> leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
<span className="text-base text-gray-500"> >
Start a conversation and click the agent’s thoughts{" "} <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]">
<span> <div className="relative w-full">
<ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " /> <button
</span>{" "} onClick={() => clearChat()}
to inspect the chaining process. className="absolute top-2 right-2 hover:text-red-500"
</span> >
</div> <FaEraser className="w-4 h-4" />
</div> </button>
)} </div>
<div ref={ref}></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">
</div> {chatHistory.length > 0 ? (
<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"> chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
<div className="relative w-full mt-1 rounded-md shadow-sm"> ) : (
<ChatInput <div className="flex flex-col h-full text-center justify-center w-full items-center align-middle ">
chatValue={chatValue} <span>
lockChat={lockChat} 👋{" "}
sendMessage={sendMessage} <span className="text-gray-600 text-lg">
setChatValue={setChatValue} LangFlow Chat
/> </span>
</div> </span>
</div> <br />
</Dialog.Panel> <div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200">
</Transition.Child> <span className="text-base text-gray-500">
</div> Start a conversation and click the agent’s thoughts{" "}
</div> <span>
</Dialog> <ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
</Transition.Root> </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>
);
} }