Refactor WebSocket connection to new port number 7860 in Makefile and add file upload support to ChatModal component.

This commit is contained in:
Gabriel Almeida 2023-04-27 09:53:31 -03:00
commit cdc6d2994a
2 changed files with 295 additions and 293 deletions

View file

@ -27,7 +27,7 @@ run_frontend:
cd src/frontend && npm start cd src/frontend && npm start
run_backend: run_backend:
poetry run uvicorn langflow.main:app --port 5003 --reload --log-level debug poetry run uvicorn langflow.main:app --port 7860 --reload --log-level debug
build_frontend: build_frontend:
cd src/frontend && CI='' npm run build cd src/frontend && CI='' npm run build

View file

@ -1,8 +1,8 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import {
ChatBubbleOvalLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon,
LockClosedIcon, LockClosedIcon,
PaperAirplaneIcon, PaperAirplaneIcon,
} from "@heroicons/react/24/outline"; } 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 { PopUpContext } from "../../contexts/popUpContext";
@ -20,311 +20,313 @@ 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 { updateFlow } = useContext(TabsContext); const { updateFlow } = useContext(TabsContext);
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, setWs] = useState<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false); const [lockChat, setLockChat] = useState(false);
const addChatHistory = ( const addChatHistory = (
message: string, message: string,
isSend: boolean, isSend: boolean,
thought?: string, thought?: string,
files?: Array<any> files?: Array<any>
) => { ) => {
setChatHistory((old) => { setChatHistory((old) => {
let newChat = _.cloneDeep(old); let newChat = _.cloneDeep(old);
if (files) { if (files) {
newChat.push({ message, isSend, files,thought }); newChat.push({ message, isSend, files, thought });
} else if (thought) { } else if (thought) {
newChat.push({ message, isSend, thought }); newChat.push({ message, isSend, thought });
} else { } else {
newChat.push({ message, isSend }); newChat.push({ message, isSend });
} }
return newChat; return newChat;
}); });
}; };
function connectWS() { function connectWS() {
const newWs = new WebSocket(`ws://127.0.0.1:5003/chat/${flow.id}`); const newWs = new WebSocket(`ws://127.0.0.1:7860/chat/${flow.id}`);
newWs.onopen = () => { newWs.onopen = () => {
console.log("WebSocket connection established!"); console.log("WebSocket connection established!");
}; };
newWs.onmessage = (event) => { newWs.onmessage = (event) => {
try { try {
setLockChat(false); setLockChat(false);
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
console.log("Received data:", data); console.log("Received data:", data);
//get chat history //get chat history
if (Array.isArray(data)) { if (Array.isArray(data)) {
console.log(data); console.log(data);
setChatHistory((_) => { setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = []; let newChatHistory: ChatMessageType[] = [];
data.forEach( data.forEach(
(chatItem: { (chatItem: {
intermediate_steps?: "string"; intermediate_steps?: "string";
is_bot: boolean; is_bot: boolean;
message: string; message: string;
type: string; type: string;
files?: Array<any>; files?: Array<any>;
}) => { }) => {
if (chatItem.message) { if (chatItem.message) {
newChatHistory.push( newChatHistory.push(
chatItem.files chatItem.files
? { ? {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: chatItem.message, message: chatItem.message,
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
files: chatItem.files, files: chatItem.files,
} }
: { : {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: chatItem.message, message: chatItem.message,
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
} }
); );
} }
} }
); );
return newChatHistory; return newChatHistory;
}); });
} }
if (data.type === "end") { if (data.type === "end") {
if (data.files) { if (data.files) {
addChatHistory( addChatHistory(
data.message, data.message,
false, false,
data.intermediate_steps, data.intermediate_steps,
data.files data.files
); );
} else { } else {
addChatHistory(data.message, false, data.intermediate_steps); addChatHistory(data.message, false, data.intermediate_steps);
} }
} }
if (data.type == "file") { if (data.type == "file") {
console.log(data); console.log(data);
} }
} catch (error) { } catch (error) {
if(event.data!=="Error: 1005"){ if (event.data !== "Error: 1005") {
setErrorData({ title: event.data }); setErrorData({ title: event.data });
}
}
};
newWs.onclose = (_) => {
if (open) {
setLockChat(false);
setTimeout(() => {
connectWS();
}, 100);
}
};
setWs(newWs);
} return newWs;
} }
};
newWs.onclose = (_) => {
if (open) {
setLockChat(false);
setTimeout(() => {
connectWS();
}, 100);
}
};
setWs(newWs);
return newWs; useEffect(() => {
} let newWs = connectWS();
return () => {
newWs.close();
};
}, []);
useEffect(() => { async function sendAll(data: sendAllProps) {
let newWs = connectWS(); if (ws) {
return () => { ws.send(JSON.stringify(data));
newWs.close(); }
}; }
}, []);
async function sendAll(data: sendAllProps) { useEffect(() => {
if (ws) { if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
ws.send(JSON.stringify(data)); }, [chatHistory]);
}
}
useEffect(() => { useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); if (ws && ws.readyState === ws.CLOSED) {
}, [chatHistory]); setLockChat(false);
}
}, [lockChat]);
useEffect(() => { function validateNode(n: NodeType): Array<string> {
if (ws && ws.readyState === ws.CLOSED) { if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
setLockChat(false); setNoticeData({
} title:
}, [lockChat]); "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 [];
}
function validateNode(n: NodeType): Array<string> { const {
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) { type,
setNoticeData({ node: { template },
title: } = n.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!",
});
return [];
}
const { return Object.keys(template).reduce(
type, (errors: Array<string>, t) =>
node: { template }, errors.concat(
} = n.data; 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[]
);
}
return Object.keys(template).reduce( function validateNodes() {
(errors: Array<string>, t) => return reactFlowInstance
errors.concat( .getNodes()
template[t].required && .flatMap((n: NodeType) => validateNode(n));
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() { const ref = useRef(null);
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);
function sendMessage() { sendAll({
if (chatValue !== "") { ...reactFlowInstance.toObject(),
let nodeValidationErrors = validateNodes(); message,
if (nodeValidationErrors.length === 0) { chatHistory,
setLockChat(true); name: flow.name,
let message = chatValue; description: flow.description,
setChatValue(""); });
addChatHistory(message, true); } 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 }));
}
sendAll({ const { closePopUp } = useContext(PopUpContext);
...reactFlowInstance.toObject(), function setModalOpen(x: boolean) {
message, setOpen(x);
chatHistory, if (x === false) {
name: flow.name, setTimeout(() => {
description: flow.description, closePopUp();
}); }, 300);
} else { }
setErrorData({ }
title: "Oops! Looks like you missed some required information:", return (
list: nodeValidationErrors, <Transition.Root show={open} appear={true} as={Fragment}>
}); <Dialog
} as="div"
} else { className="relative z-10"
setErrorData({ onClose={setModalOpen}
title: "Error sending message", initialFocus={ref}
list: ["The message cannot be empty."], >
}); <Transition.Child
} as={Fragment}
} enter="ease-out duration-300"
function clearChat() { enterFrom="opacity-0"
setChatHistory([]); enterTo="opacity-100"
ws.send(JSON.stringify({ clear_history: true })); 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>
const { closePopUp } = useContext(PopUpContext); <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
if (x === false) { as={Fragment}
setTimeout(() => { enter="ease-out duration-300"
closePopUp(); enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
}, 300); enterTo="opacity-100 translate-y-0 sm:scale-100"
} leave="ease-in duration-200"
} leaveFrom="opacity-100 translate-y-0 sm:scale-100"
return ( leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
<Transition.Root show={open} appear={true} as={Fragment}> >
<Dialog <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]">
as="div" <div className="relative w-full">
className="relative z-10" <button
onClose={setModalOpen} onClick={() => clearChat()}
initialFocus={ref} className="absolute top-2 right-2 hover:text-red-500"
> >
<Transition.Child <FaEraser className="w-4 h-4" />
as={Fragment} </button>
enter="ease-out duration-300" </div>
enterFrom="opacity-0" <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">
enterTo="opacity-100" {chatHistory.length > 0 ? (
leave="ease-in duration-200" chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
leaveFrom="opacity-100" ) : (
leaveTo="opacity-0" <div className="flex flex-col h-full text-center justify-center w-full items-center align-middle ">
> <span>
<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> <span className="text-gray-600 text-lg">
LangFlow Chat
<div className="fixed inset-0 z-10 overflow-y-auto"> </span>
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> </span>
<Transition.Child <br />
as={Fragment} <div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200">
enter="ease-out duration-300" <span className="text-base text-gray-500">
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" Start a conversation and click the agent’s thoughts{" "}
enterTo="opacity-100 translate-y-0 sm:scale-100" <span>
leave="ease-in duration-200" <ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
leaveFrom="opacity-100 translate-y-0 sm:scale-100" </span>{" "}
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" to inspect the chaining process.
> </span>
<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>
<div className="relative w-full"> </div>
<button )}
onClick={() => clearChat()} <div ref={ref}></div>
className="absolute top-2 right-2 hover:text-red-500" </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">
<FaEraser className="w-4 h-4" /> <div className="relative w-full mt-1 rounded-md shadow-sm">
</button> <ChatInput
</div> chatValue={chatValue}
<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"> lockChat={lockChat}
{chatHistory.length > 0 ? ( sendMessage={sendMessage}
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />) setChatValue={setChatValue}
) : ( />
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle "> </div>
<span> </div>
👋 <span className="text-gray-600 text-lg">LangFlow Chat</span> </Dialog.Panel>
</span> </Transition.Child>
<br /> </div>
<div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200"> </div>
<span className="text-base text-gray-500"> </Dialog>
Start a conversation and click the agent’s thoughts{" "} </Transition.Root>
<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>
);
} }