Merge branch 'zustand/io/migration' of github.com:logspace-ai/langflow into zustand/io/migration
This commit is contained in:
commit
9af9647fb3
37 changed files with 458 additions and 255 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import Convert from "ansi-to-html";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState, useRef } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import rehypeMathjax from "rehype-mathjax";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
|
@ -12,12 +12,14 @@ import IconComponent from "../../../components/genericIconComponent";
|
|||
import { chatMessagePropsType } from "../../../types/components";
|
||||
import { classNames } from "../../../utils/utils";
|
||||
import FileCard from "../fileComponent";
|
||||
import useFlowStore from "../../../stores/flowStore";
|
||||
|
||||
export default function ChatMessage({
|
||||
chat,
|
||||
lockChat,
|
||||
lastMessage,
|
||||
updateChat,
|
||||
setLockChat
|
||||
}: chatMessagePropsType): JSX.Element {
|
||||
const convert = new Convert({ newline: true });
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
|
@ -29,6 +31,16 @@ export default function ChatMessage({
|
|||
const chatMessageString = chat.message ? chat.message.toString() : "";
|
||||
const [chatMessage, setChatMessage] = useState(chatMessageString);
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const eventSource = useRef<EventSource | undefined>(undefined);
|
||||
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
|
||||
const chatMessageRef = useRef(chatMessage);
|
||||
|
||||
// Sync ref with state
|
||||
useEffect(() => {
|
||||
chatMessageRef.current = chatMessage;
|
||||
}, [chatMessage]);
|
||||
|
||||
|
||||
|
||||
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
|
||||
// probably the message is empty when we have a stream_url
|
||||
|
|
@ -36,49 +48,52 @@ export default function ChatMessage({
|
|||
const streamChunks = (url: string) => {
|
||||
setIsStreaming(true); // Streaming starts
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const eventSource = new EventSource(url);
|
||||
eventSource.onmessage = (event) => {
|
||||
eventSource.current = new EventSource(url);
|
||||
eventSource.current.onmessage = (event) => {
|
||||
let parsedData = JSON.parse(event.data);
|
||||
if (parsedData.chunk) {
|
||||
setChatMessage((prev) => prev + parsedData.chunk);
|
||||
}
|
||||
};
|
||||
eventSource.onerror = (event) => {
|
||||
eventSource.current.onerror = (event) => {
|
||||
setIsStreaming(false);
|
||||
eventSource.current?.close();
|
||||
setStreamUrl(undefined);
|
||||
reject(new Error("Streaming failed"));
|
||||
setIsStreaming(false);
|
||||
eventSource.close();
|
||||
};
|
||||
eventSource.addEventListener("close", (event) => {
|
||||
setStreamUrl(null); // Update state to reflect the stream is closed
|
||||
resolve(true);
|
||||
eventSource.current.addEventListener("close", (event) => {
|
||||
setStreamUrl(undefined); // Update state to reflect the stream is closed
|
||||
eventSource.current?.close();
|
||||
setIsStreaming(false);
|
||||
eventSource.close();
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (streamUrl && chat.message === "") {
|
||||
console.log("chatMessage", chatMessage);
|
||||
if (streamUrl && !isStreaming) {
|
||||
setLockChat(true);
|
||||
streamChunks(streamUrl)
|
||||
.then(() => {
|
||||
setLockChat(false);
|
||||
if (updateChat) {
|
||||
updateChat(chat, chatMessage, streamUrl);
|
||||
updateChat(chat, chatMessageRef.current);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setLockChat(false);
|
||||
});
|
||||
}
|
||||
}, [streamUrl]);
|
||||
}, [streamUrl, chatMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
// This effect is specifically for calling updateChat after streaming ends
|
||||
if (!isStreaming && streamUrl) {
|
||||
if (updateChat) {
|
||||
updateChat(chat, chatMessage, streamUrl);
|
||||
}
|
||||
return () => {
|
||||
eventSource.current?.close();
|
||||
}
|
||||
}, [isStreaming]);
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const element = document.getElementById("last-chat-message");
|
||||
|
|
@ -207,7 +222,7 @@ dark:prose-invert"
|
|||
},
|
||||
]}
|
||||
activeTab={"0"}
|
||||
setActiveTab={() => {}}
|
||||
setActiveTab={() => { }}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
|
|
@ -264,33 +279,33 @@ dark:prose-invert"
|
|||
<span className="prose text-primary word-break-break-word dark:prose-invert">
|
||||
{promptOpen
|
||||
? template?.split("\n")?.map((line, index) => {
|
||||
const regex = /{([^}]+)}/g;
|
||||
let match;
|
||||
let parts: Array<JSX.Element | string> = [];
|
||||
let lastIndex = 0;
|
||||
while ((match = regex.exec(line)) !== null) {
|
||||
// Push text up to the match
|
||||
if (match.index !== lastIndex) {
|
||||
parts.push(line.substring(lastIndex, match.index));
|
||||
}
|
||||
// Push div with matched text
|
||||
if (chat.message[match[1]]) {
|
||||
parts.push(
|
||||
<span className="chat-message-highlight">
|
||||
{chat.message[match[1]]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const regex = /{([^}]+)}/g;
|
||||
let match;
|
||||
let parts: Array<JSX.Element | string> = [];
|
||||
let lastIndex = 0;
|
||||
while ((match = regex.exec(line)) !== null) {
|
||||
// Push text up to the match
|
||||
if (match.index !== lastIndex) {
|
||||
parts.push(line.substring(lastIndex, match.index));
|
||||
}
|
||||
// Push div with matched text
|
||||
if (chat.message[match[1]]) {
|
||||
parts.push(
|
||||
<span className="chat-message-highlight">
|
||||
{chat.message[match[1]]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Update last index
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
// Push text after the last match
|
||||
if (lastIndex !== line.length) {
|
||||
parts.push(line.substring(lastIndex));
|
||||
}
|
||||
return <p>{parts}</p>;
|
||||
})
|
||||
// Update last index
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
// Push text after the last match
|
||||
if (lastIndex !== line.length) {
|
||||
parts.push(line.substring(lastIndex));
|
||||
}
|
||||
return <p>{parts}</p>;
|
||||
})
|
||||
: chatMessage}
|
||||
</span>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default function NewChatView({
|
|||
const inputIds = inputs.map((obj) => obj.id);
|
||||
const outputIds = outputs.map((obj) => obj.id);
|
||||
const outputTypes = outputs.map((obj) => obj.type);
|
||||
const updateFlowPool = useFlowStore((state)=>state.updateFlowPool)
|
||||
|
||||
useEffect(() => {
|
||||
if (!outputTypes.includes("ChatOutput")) {
|
||||
|
|
@ -67,14 +68,12 @@ export default function NewChatView({
|
|||
const { sender, message, sender_name, stream_url } = output.data
|
||||
.artifacts as ChatOutputType;
|
||||
|
||||
const componentId = output.id + index;
|
||||
|
||||
const is_ai = sender === "Machine" || sender === null;
|
||||
return {
|
||||
isSend: !is_ai,
|
||||
message: message,
|
||||
sender_name,
|
||||
id: componentId,
|
||||
componentId: output.id,
|
||||
stream_url: stream_url,
|
||||
};
|
||||
} catch (e) {
|
||||
|
|
@ -83,7 +82,7 @@ export default function NewChatView({
|
|||
isSend: false,
|
||||
message: "Error parsing message",
|
||||
sender_name: "Error",
|
||||
id: output.id + index,
|
||||
componentId: output.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -120,27 +119,24 @@ export default function NewChatView({
|
|||
function updateChat(
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url: string | null
|
||||
) {
|
||||
if (message === "") return;
|
||||
console.log(`updateChat: ${message}`);
|
||||
console.log("chatHistory:", chatHistory);
|
||||
chat.message = message;
|
||||
chat.stream_url = stream_url;
|
||||
stream_url?: string
|
||||
) {
|
||||
if (message === "") return;
|
||||
chat.message = message;
|
||||
// chat is one of the chatHistory
|
||||
setChatHistory((oldChatHistory) => {
|
||||
const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
|
||||
|
||||
if (index === -1) return oldChatHistory;
|
||||
let newChatHistory = _.cloneDeep(oldChatHistory);
|
||||
newChatHistory = [
|
||||
...newChatHistory.slice(0, index),
|
||||
chat,
|
||||
...newChatHistory.slice(index + 1),
|
||||
];
|
||||
console.log("newChatHistory:", newChatHistory);
|
||||
return newChatHistory;
|
||||
});
|
||||
updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:"Machine"})
|
||||
// setChatHistory((oldChatHistory) => {
|
||||
// const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
|
||||
// if (index === -1) return oldChatHistory;
|
||||
// let newChatHistory = _.cloneDeep(oldChatHistory);
|
||||
// newChatHistory = [
|
||||
// ...newChatHistory.slice(0, index),
|
||||
// chat,
|
||||
// ...newChatHistory.slice(index + 1),
|
||||
// ];
|
||||
// console.log("newChatHistory:", newChatHistory);
|
||||
// return newChatHistory;
|
||||
// });
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -164,10 +160,11 @@ export default function NewChatView({
|
|||
{chatHistory?.length > 0 ? (
|
||||
chatHistory.map((chat, index) => (
|
||||
<ChatMessage
|
||||
setLockChat={setLockChat}
|
||||
lockChat={lockChat}
|
||||
chat={chat}
|
||||
lastMessage={chatHistory.length - 1 === index ? true : false}
|
||||
key={`${chat.id}-${index}`}
|
||||
key={`${chat.componentId}-${index}`}
|
||||
updateChat={updateChat}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
sourceHandleType,
|
||||
targetHandleType,
|
||||
} from "../types/flow";
|
||||
import { FlowStoreType } from "../types/zustand/flow";
|
||||
import { ChatOutputType, FlowPoolObjectType, FlowStoreType, chatInputType } from "../types/zustand/flow";
|
||||
import { buildVertices } from "../utils/buildUtils";
|
||||
import {
|
||||
cleanEdges,
|
||||
|
|
@ -51,7 +51,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
setFlowPool: (flowPool) => {
|
||||
set({ flowPool });
|
||||
},
|
||||
addDataToFlowPool: (data: any, nodeId: string) => {
|
||||
addDataToFlowPool: (data: FlowPoolObjectType, nodeId: string) => {
|
||||
let newFlowPool = cloneDeep({ ...get().flowPool });
|
||||
if (!newFlowPool[nodeId]) newFlowPool[nodeId] = [data];
|
||||
else {
|
||||
|
|
@ -59,6 +59,28 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
}
|
||||
get().setFlowPool(newFlowPool);
|
||||
},
|
||||
updateFlowPool:(nodeId:string,data:FlowPoolObjectType| ChatOutputType | chatInputType,buildId?:string)=>{
|
||||
let newFlowPool = cloneDeep({ ...get().flowPool });
|
||||
if (!newFlowPool[nodeId]){
|
||||
return;
|
||||
}
|
||||
else {
|
||||
let index = newFlowPool[nodeId].length-1;
|
||||
if(buildId){
|
||||
index = newFlowPool[nodeId].findIndex((flow)=>flow.id===buildId);
|
||||
}
|
||||
//check if the data is a flowpool object
|
||||
if((data as FlowPoolObjectType).data?.artifacts!==undefined){
|
||||
newFlowPool[nodeId][index] = (data as FlowPoolObjectType);
|
||||
}
|
||||
//update data artifact
|
||||
else
|
||||
{
|
||||
newFlowPool[nodeId][index].data.artifacts = data;
|
||||
}
|
||||
}
|
||||
get().setFlowPool(newFlowPool);
|
||||
},
|
||||
CleanFlowPool: () => {
|
||||
get().setFlowPool({});
|
||||
},
|
||||
|
|
@ -394,12 +416,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
}
|
||||
function handleBuildUpdate(
|
||||
vertexBuildData: VertexBuildTypeAPI,
|
||||
status: BuildStatus
|
||||
status: BuildStatus,
|
||||
buildId:string
|
||||
) {
|
||||
if (vertexBuildData && vertexBuildData.inactive_vertices) {
|
||||
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
|
||||
}
|
||||
get().addDataToFlowPool(vertexBuildData, vertexBuildData.id);
|
||||
get().addDataToFlowPool({...vertexBuildData,buildId}, vertexBuildData.id);
|
||||
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
|
||||
}
|
||||
await updateFlowInDatabase({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export type ChatMessageType = {
|
|||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
prompt?: string;
|
||||
chatKey?: string;
|
||||
id?: string;
|
||||
componentId: string;
|
||||
stream_url?: string | null;
|
||||
sender_name?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -524,10 +524,11 @@ export type chatMessagePropsType = {
|
|||
chat: ChatMessageType;
|
||||
lockChat: boolean;
|
||||
lastMessage: boolean;
|
||||
setLockChat: (lock: boolean) => void;
|
||||
updateChat: (
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url: string
|
||||
stream_url?: string
|
||||
) => void;
|
||||
};
|
||||
|
||||
|
|
@ -632,9 +633,9 @@ export type validationStatusType = {
|
|||
id: string;
|
||||
data: object | any;
|
||||
params: string;
|
||||
progress: number;
|
||||
progress?: number;
|
||||
valid: boolean;
|
||||
duration: string;
|
||||
duration?: string;
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
|
|
|
|||
|
|
@ -18,16 +18,18 @@ export type ChatOutputType = {
|
|||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
stream_url?: string;
|
||||
};
|
||||
|
||||
export type FlowPoolObjectType = {
|
||||
timestamp: string;
|
||||
valid: boolean;
|
||||
params: any;
|
||||
data: { artifacts: any; results: any | ChatOutputType | chatInputType };
|
||||
duration: string;
|
||||
progress: number;
|
||||
data: { artifacts: any | ChatOutputType | chatInputType; results: any | ChatOutputType | chatInputType };
|
||||
duration?: string;
|
||||
progress?: number;
|
||||
id: string;
|
||||
buildId: string;
|
||||
};
|
||||
|
||||
export type FlowPoolType = {
|
||||
|
|
@ -40,7 +42,7 @@ export type FlowStoreType = {
|
|||
outputs: Array<{ type: string; id: string }>;
|
||||
hasIO: boolean;
|
||||
setFlowPool: (flowPool: FlowPoolType) => void;
|
||||
addDataToFlowPool: (data: any, nodeId: string) => void;
|
||||
addDataToFlowPool: (data: FlowPoolObjectType, nodeId: string) => void;
|
||||
CleanFlowPool: () => void;
|
||||
isBuilding: boolean;
|
||||
isPending: boolean;
|
||||
|
|
@ -92,4 +94,5 @@ export type FlowStoreType = {
|
|||
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
|
||||
revertBuiltStatusFromBuilding: () => void;
|
||||
flowBuildStatus: { [key: string]: BuildStatus };
|
||||
updateFlowPool: (nodeId:string, data:FlowPoolObjectType | ChatOutputType | chatInputType,buildId?:string) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ type BuildVerticesParams = {
|
|||
flowId: string; // Assuming FlowType is the type for your flow
|
||||
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
||||
onGetOrderSuccess?: () => void;
|
||||
onBuildUpdate?: (data: VertexBuildTypeAPI, status: BuildStatus) => void; // Replace any with the actual type if it's not any
|
||||
onBuildUpdate?: (data: VertexBuildTypeAPI, status: BuildStatus,buildId:string) => void; // Replace any with the actual type if it's not any
|
||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||
onBuildError?: (title, list, idList: string[]) => void;
|
||||
onBuildStart?: (idList: string[]) => void;
|
||||
|
|
@ -48,7 +48,7 @@ export async function buildVertices({
|
|||
let orderResponse;
|
||||
try {
|
||||
orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||
} catch (error) {
|
||||
} catch (error:any) {
|
||||
console.log(error);
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
|
|
@ -59,6 +59,7 @@ export async function buildVertices({
|
|||
}
|
||||
if (onGetOrderSuccess) onGetOrderSuccess();
|
||||
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
||||
const runId = orderResponse.data.run_id;
|
||||
let vertices_layers: Array<Array<string>> = [];
|
||||
let stop = false;
|
||||
if (validateNodes) {
|
||||
|
|
@ -102,14 +103,14 @@ export async function buildVertices({
|
|||
onBuildUpdate
|
||||
) {
|
||||
// If it is, skip building and set the state to inactive
|
||||
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE);
|
||||
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE,runId);
|
||||
buildResults.push(false);
|
||||
continue;
|
||||
}
|
||||
await buildVertex({
|
||||
flowId,
|
||||
id,
|
||||
onBuildUpdate,
|
||||
onBuildUpdate:(data: VertexBuildTypeAPI, status: BuildStatus) => {if(onBuildUpdate) onBuildUpdate(data, status,runId)},
|
||||
onBuildError,
|
||||
verticesIds,
|
||||
buildResults,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
tweakType,
|
||||
} from "../types/components";
|
||||
import { FlowType, NodeType } from "../types/flow";
|
||||
import { FlowState, FlowsState } from "../types/tabs";
|
||||
import { FlowState } from "../types/tabs";
|
||||
import { buildTweaks } from "./reactflowUtils";
|
||||
|
||||
export function classNames(...classes: Array<string>): string {
|
||||
|
|
@ -217,12 +217,8 @@ export function groupByFamily(
|
|||
}));
|
||||
}
|
||||
|
||||
export function buildInputs(flowState?: FlowState): string {
|
||||
return flowState &&
|
||||
flowState.input_keys &&
|
||||
Object.keys(flowState.input_keys!).length > 0
|
||||
? JSON.stringify(flowState.input_keys)
|
||||
: '{"input": "message"}';
|
||||
export function buildInputs(): string {
|
||||
return '{"input_value": "message"}';
|
||||
}
|
||||
|
||||
export function getRandomElement<T>(array: T[]): T {
|
||||
|
|
@ -323,7 +319,7 @@ export function getPythonApiCode(
|
|||
// node.data.id
|
||||
// }
|
||||
const tweaks = buildTweaks(flow);
|
||||
const inputs = buildInputs(flowState);
|
||||
const inputs = buildInputs();
|
||||
return `import requests
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -382,12 +378,10 @@ export function getCurlCode(
|
|||
): string {
|
||||
const flowId = flow.id;
|
||||
const tweaks = buildTweaks(flow);
|
||||
const inputs = buildInputs(flowState);
|
||||
const inputs = buildInputs();
|
||||
|
||||
return `curl -X POST \\
|
||||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/process/${flowId} \\
|
||||
${window.location.protocol}//${window.location.host}/api/v1/run/${flowId} \\
|
||||
-H 'Content-Type: application/json'\\${
|
||||
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
|
||||
}
|
||||
|
|
@ -410,7 +404,7 @@ export function getPythonCode(
|
|||
): string {
|
||||
const flowName = flow.name;
|
||||
const tweaks = buildTweaks(flow);
|
||||
const inputs = buildInputs(flowState);
|
||||
const inputs = buildInputs();
|
||||
return `from langflow import load_flow_from_json
|
||||
TWEAKS = ${
|
||||
tweak && tweak.length > 0
|
||||
|
|
@ -435,7 +429,7 @@ export function getWidgetCode(
|
|||
): string {
|
||||
const flowId = flow.id;
|
||||
const flowName = flow.name;
|
||||
const inputs = buildInputs(flowState);
|
||||
const inputs = buildInputs();
|
||||
let chat_input_field = getChatInputField(flow, flowState);
|
||||
|
||||
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue