diff --git a/src/backend/base/langflow/api/v1/monitor.py b/src/backend/base/langflow/api/v1/monitor.py index 22b4a663d..e419ed5bf 100644 --- a/src/backend/base/langflow/api/v1/monitor.py +++ b/src/backend/base/langflow/api/v1/monitor.py @@ -1,8 +1,7 @@ from typing import List, Optional - +from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query -from langflow.api.v1.schemas import MessageIds from langflow.services.deps import get_monitor_service from langflow.services.monitor.schema import ( MessageModelRequest, @@ -68,13 +67,13 @@ async def get_messages( raise HTTPException(status_code=500, detail=str(e)) -@router.post("/messages", status_code=204) +@router.delete("/messages", status_code=204) async def delete_messages( - message_ids: MessageIds, + message_ids: List[int], monitor_service: MonitorService = Depends(get_monitor_service), ): try: - monitor_service.delete_messages(message_ids=message_ids.ids) + monitor_service.delete_messages(message_ids=message_ids) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -87,9 +86,10 @@ async def update_message( ): try: message_dict = message.model_dump(exclude_none=True) - df = monitor_service.update_message(message_id=message_id, **message_dict) - dicts = df.to_dict(orient="records") - return [MessageModelResponse(**d) for d in dicts] + message_dict.pop("index", None) + monitor_service.update_message(message_id=message_id, **message_dict) + return MessageModelResponse(index=message_id, **message_dict) + except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/src/backend/base/langflow/api/v1/schemas.py b/src/backend/base/langflow/api/v1/schemas.py index c9dc28bcf..9ccdb0085 100644 --- a/src/backend/base/langflow/api/v1/schemas.py +++ b/src/backend/base/langflow/api/v1/schemas.py @@ -321,6 +321,3 @@ class FlowDataRequest(BaseModel): class ConfigResponse(BaseModel): frontend_timeout: int - -class MessageIds(BaseModel): - ids: List[int] diff --git a/src/backend/base/langflow/services/monitor/service.py b/src/backend/base/langflow/services/monitor/service.py index ea5e1d9b2..ab5a87f08 100644 --- a/src/backend/base/langflow/services/monitor/service.py +++ b/src/backend/base/langflow/services/monitor/service.py @@ -73,7 +73,7 @@ class MonitorService(Service): valid: Optional[bool] = None, order_by: Optional[str] = "timestamp", ): - query = "SELECT index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds" + query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds" conditions = [] if flow_id: conditions.append(f"flow_id = '{flow_id}'") @@ -92,6 +92,8 @@ class MonitorService(Service): with duckdb.connect(str(self.db_path)) as conn: df = conn.execute(query).df() + print(query) + return df.to_dict(orient="records") def delete_vertex_builds(self, flow_id: Optional[str] = None): @@ -113,7 +115,7 @@ class MonitorService(Service): return self.exec_query(query) def update_message(self, message_id: int, **kwargs): - query = f"UPDATE messages SET {', '.join(f'{k} = {v}' for k, v in kwargs.items())} WHERE id = {message_id}" + query = f"""UPDATE messages SET {', '.join(f"{k} = '{v}'" for k, v in kwargs.items())} WHERE index = {message_id}""" return self.exec_query(query) diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index d630c474f..91abedd18 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -2,11 +2,11 @@ import axios, { AxiosError, AxiosInstance } from "axios"; import { useContext, useEffect } from "react"; import { Cookies } from "react-cookie"; import { renewAccessToken } from "."; -import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../constants/constants"; import { BuildStatus } from "../../constants/enums"; import { AuthContext } from "../../contexts/authContext"; import useAlertStore from "../../stores/alertStore"; import useFlowStore from "../../stores/flowStore"; +import { checkDuplicateRequestAndStoreRequest } from "./helpers/check-duplicate-requests"; // Create a new Axios instance const api: AxiosInstance = axios.create({ @@ -48,7 +48,7 @@ function ApiInterceptor() { } await clearBuildVerticesState(error); return Promise.reject(error); - } + }, ); const isAuthorizedURL = (url) => { @@ -65,10 +65,10 @@ function ApiInterceptor() { const parsedURL = new URL(url); const isDomainAllowed = authorizedDomains.some( - (domain) => parsedURL.origin === new URL(domain).origin + (domain) => parsedURL.origin === new URL(domain).origin, ); const isEndpointAllowed = authorizedEndpoints.some((endpoint) => - parsedURL.pathname.includes(endpoint) + parsedURL.pathname.includes(endpoint), ); return isDomainAllowed || isEndpointAllowed; @@ -81,28 +81,12 @@ function ApiInterceptor() { // Request interceptor to add access token to every request const requestInterceptor = api.interceptors.request.use( (config) => { - const lastUrl = localStorage.getItem("lastUrlCalled"); - const lastMethodCalled = localStorage.getItem("lastMethodCalled"); + const checkRequest = checkDuplicateRequestAndStoreRequest(config); - const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) => - config?.url!.includes(request), - ); - - if ( - config?.url === lastUrl && - !isContained && - lastMethodCalled === config.method - ) { - return Promise.reject("Duplicate request"); + if (!checkRequest) { + return Promise.reject("Duplicate request."); } - localStorage.setItem("lastUrlCalled", config.url ?? ""); - localStorage.setItem("lastMethodCalled", config.method ?? ""); - localStorage.setItem( - "lastRequestData", - JSON.stringify(config.data) ?? "", - ); - const accessToken = cookies.get("access_token_lf"); if (accessToken && !isAuthorizedURL(config?.url)) { config.headers["Authorization"] = `Bearer ${accessToken}`; @@ -112,7 +96,7 @@ function ApiInterceptor() { }, (error) => { return Promise.reject(error); - } + }, ); return () => { @@ -144,7 +128,7 @@ function ApiInterceptor() { if (error?.config?.headers) { delete error.config.headers["Authorization"]; error.config.headers["Authorization"] = `Bearer ${cookies.get( - "access_token_lf" + "access_token_lf", )}`; const response = await axios.request(error.config); return response; diff --git a/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts new file mode 100644 index 000000000..79a47c7a7 --- /dev/null +++ b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts @@ -0,0 +1,30 @@ +import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../../constants/constants"; + +export function checkDuplicateRequestAndStoreRequest(config) { + const lastUrl = localStorage.getItem("lastUrlCalled"); + const lastMethodCalled = localStorage.getItem("lastMethodCalled"); + const lastRequestTime = localStorage.getItem("lastRequestTime"); + + const currentTime = Date.now(); + + const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) => + config?.url!.includes(request), + ); + + if ( + config?.url === lastUrl && + !isContained && + lastMethodCalled === config.method && + lastMethodCalled === "get" && // Assuming you want to check only for GET requests + lastRequestTime && + currentTime - parseInt(lastRequestTime, 10) < 800 + ) { + return false; + } + + localStorage.setItem("lastUrlCalled", config.url ?? ""); + localStorage.setItem("lastMethodCalled", config.method ?? ""); + localStorage.setItem("lastRequestTime", currentTime.toString()); + + return true; +} diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index c1d3b24a0..8f01d1908 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -28,6 +28,7 @@ import { UploadFileTypeAPI, errorsTypeAPI, } from "./../../types/api/index"; +import { Message } from "../../types/messages"; /** * Fetches all objects from the API endpoint. @@ -1039,8 +1040,30 @@ export async function getMessagesTable( return { rows: rows.data, columns }; } -export async function deleteMessagesFn(ids: number[]) { - return await api.post(`${BASE_URL_API}monitor/messages`, { - ids, +export async function getSessions(id?: string): Promise> { + const config = {}; + if (id) { + config["params"] = { flow_id: id }; + } + const rows = await api.get(`${BASE_URL_API}monitor/messages`, config); + const sessions = new Set(); + rows.data.forEach((row) => { + sessions.add(row.session_id); }); + return Array.from(sessions); +} + +export async function deleteMessagesFn(ids: number[]) { + try { + return await api.delete(`${BASE_URL_API}monitor/messages`, { + data: ids, + }); + } catch (error) { + console.error("Error deleting flows:", error); + throw error; + } +} + +export async function updateMessageApi(data: Message) { + return await api.post(`${BASE_URL_API}monitor/messages/${data.index}`, data); } diff --git a/src/frontend/src/modals/IOModal/components/chatView/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/index.tsx index 61cf44516..6b122cf4c 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/index.tsx @@ -1,5 +1,11 @@ import { useEffect, useRef, useState } from "react"; import IconComponent from "../../../../components/genericIconComponent"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "../../../../components/ui/select"; import { CHAT_FIRST_INITIAL_TEXT, CHAT_SECOND_INITIAL_TEXT, @@ -18,12 +24,6 @@ import { chatViewProps } from "../../../../types/components"; import { classNames } from "../../../../utils/utils"; import ChatInput from "./chatInput"; import ChatMessage from "./chatMessage"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, -} from "../../../../components/ui/select"; export default function ChatView({ sendMessage, diff --git a/src/frontend/src/modals/IOModal/index.tsx b/src/frontend/src/modals/IOModal/index.tsx index 82350b57e..100a48785 100644 --- a/src/frontend/src/modals/IOModal/index.tsx +++ b/src/frontend/src/modals/IOModal/index.tsx @@ -25,6 +25,7 @@ import { cn } from "../../utils/utils"; import BaseModal from "../baseModal"; import IOFieldView from "./components/IOFieldView"; import ChatView from "./components/chatView"; +import { getSessions } from "../../controllers/API"; export default function IOModal({ children, @@ -78,6 +79,7 @@ export default function IOModal({ const isBuilding = useFlowStore((state) => state.isBuilding); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const setNode = useFlowStore((state) => state.setNode); + const [sessions, setSessions] = useState([]); async function updateVertices() { return updateVerticesOrder(currentFlow!.id, null); @@ -113,6 +115,11 @@ export default function IOModal({ useEffect(() => { setSelectedViewField(startView()); + // if (haveChat) { + // getSessions().then((sessions) => { + // setSessions(sessions); + // }); + // } }, [open]); return ( @@ -160,6 +167,9 @@ export default function IOModal({ {outputs.length > 0 && ( Outputs )} + {haveChat && ( + History + )} diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-messages-table.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-messages-table.tsx index deac7c1f0..7f3a74352 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-messages-table.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-messages-table.tsx @@ -1,14 +1,15 @@ import { useEffect } from "react"; import { getMessagesTable } from "../../../../../controllers/API"; +import { useMessagesStore } from "../../../../../stores/messagesStore"; -const useMessagesTable = (setColumns, setRows, setMessages) => { +const useMessagesTable = (setColumns) => { + const setMessages = useMessagesStore((state) => state.setMessages); useEffect(() => { const fetchData = async () => { try { const data = await getMessagesTable("union", undefined, ["index"]); const { columns, rows } = data; setColumns(columns); - setRows(rows); setMessages(rows); } catch (error) { console.error("Error fetching messages:", error); diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx index 01c4eb446..d7f4d5202 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx @@ -2,7 +2,6 @@ import { deleteMessagesFn } from "../../../../../controllers/API"; import { useMessagesStore } from "../../../../../stores/messagesStore"; const useRemoveMessages = ( - setRows, setSelectedRows, setSuccessData, setErrorData, @@ -12,22 +11,13 @@ const useRemoveMessages = ( const handleRemoveMessages = async () => { try { - // Call the deleteMessagesFn to perform the deletion await deleteMessagesFn(selectedRows); - - // Assuming deleteMessages is a separate function that updates state after deletion - const res = await deleteMessages(selectedRows); - setRows(res); - - // Clear the selected rows + deleteMessages(selectedRows); setSelectedRows([]); - - // Set success message setSuccessData({ title: "Messages deleted successfully.", }); } catch (error) { - // Set error message setErrorData({ title: "Error deleting messages.", }); diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-updateMessage.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-updateMessage.tsx new file mode 100644 index 000000000..7165a3cc5 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-updateMessage.tsx @@ -0,0 +1,29 @@ +import { useMessagesStore } from "../../../../../stores/messagesStore"; +import { Message } from "../../../../../types/messages"; +import { updateMessageApi } from "../../../../../controllers/API"; + +const useUpdateMessage = (setSuccessData, setErrorData) => { + const updateMessage = useMessagesStore((state) => state.updateMessage); + + const handleUpdate = async (data: Message) => { + try { + await updateMessageApi(data); + + updateMessage(data); + + // Set success message + setSuccessData({ + title: "Messages updated successfully.", + }); + } catch (error) { + // Set error message + setErrorData({ + title: "Error updating messages.", + }); + } + }; + + return { handleUpdate }; +}; + +export default useUpdateMessage; diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx index 48ab16092..3b23a4cdb 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx @@ -1,4 +1,9 @@ -import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; +import { + CellEditRequestEvent, + ColDef, + ColGroupDef, + SelectionChangedEvent, +} from "ag-grid-community"; import { useState } from "react"; import TableComponent from "../../../../components/tableComponent"; import { Card, CardContent } from "../../../../components/ui/card"; @@ -7,12 +12,11 @@ import { useMessagesStore } from "../../../../stores/messagesStore"; import HeaderMessagesComponent from "./components/headerMessages"; import useMessagesTable from "./hooks/use-messages-table"; import useRemoveMessages from "./hooks/use-remove-messages"; +import useUpdateMessage from "./hooks/use-updateMessage"; export default function MessagesPage() { - const setMessages = useMessagesStore((state) => state.setMessages); - const [columns, setColumns] = useState>([]); - const [rows, setRows] = useState([]); + const messages = useMessagesStore((state) => state.messages); const setErrorData = useAlertStore((state) => state.setErrorData); const setSuccessData = useAlertStore((state) => state.setSuccessData); @@ -20,14 +24,26 @@ export default function MessagesPage() { const [selectedRows, setSelectedRows] = useState([]); const { handleRemoveMessages } = useRemoveMessages( - setRows, setSelectedRows, setSuccessData, setErrorData, selectedRows, ); - useMessagesTable(setColumns, setRows, setMessages); + const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData); + + useMessagesTable(setColumns); + + function handleUpdateMessage(event: CellEditRequestEvent) { + const newValue = event.newValue; + const field = event.column.getColId(); + const row = event.data; + const data = { + ...row, + [field]: newValue, + }; + handleUpdate(data); + } return (
@@ -40,6 +56,10 @@ export default function MessagesPage() { { + handleUpdateMessage(event); + }} editable={["Sender", "Sender Name", "Message"]} overlayNoRowsTemplate="No data available" onSelectionChanged={(event: SelectionChangedEvent) => { @@ -51,7 +71,7 @@ export default function MessagesPage() { suppressRowClickSelection={true} pagination={true} columnDefs={columns} - rowData={rows} + rowData={messages} /> diff --git a/src/frontend/src/stores/messagesStore.ts b/src/frontend/src/stores/messagesStore.ts index 21937f910..df1a3c15c 100644 --- a/src/frontend/src/stores/messagesStore.ts +++ b/src/frontend/src/stores/messagesStore.ts @@ -17,7 +17,7 @@ export const useMessagesStore = create((set, get) => ({ updateMessage: (message) => { set(() => ({ messages: get().messages.map((msg) => - msg.id === message.id ? message : msg, + msg.index === message.index ? message : msg, ), })); }, diff --git a/src/frontend/src/types/messages/index.ts b/src/frontend/src/types/messages/index.ts index 6351479e4..738e16258 100644 --- a/src/frontend/src/types/messages/index.ts +++ b/src/frontend/src/types/messages/index.ts @@ -9,3 +9,5 @@ type Message = { timestamp: string; id: string; }; + +export type { Message }; diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index 71ff76f55..6f995f5ec 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -48,8 +48,16 @@ export type FlowStoreType = { onFlowPage: boolean; setOnFlowPage: (onFlowPage: boolean) => void; flowPool: FlowPoolType; - inputs: Array<{ type: string; id: string; displayName: string }>; - outputs: Array<{ type: string; id: string; displayName: string }>; + inputs: Array<{ + type: string; + id: string; + displayName: string; + }>; + outputs: Array<{ + type: string; + id: string; + displayName: string; + }>; hasIO: boolean; setFlowPool: (flowPool: FlowPoolType) => void; addDataToFlowPool: (data: FlowPoolObjectType, nodeId: string) => void; diff --git a/src/frontend/src/types/zustand/messages/index.ts b/src/frontend/src/types/zustand/messages/index.ts index 30d06229d..44915d2c3 100644 --- a/src/frontend/src/types/zustand/messages/index.ts +++ b/src/frontend/src/types/zustand/messages/index.ts @@ -1,3 +1,5 @@ +import { Message } from "../../messages"; + export type MessagesStoreType = { messages: Message[]; setMessages: (messages: Message[]) => void; @@ -5,5 +7,5 @@ export type MessagesStoreType = { removeMessage: (message: Message) => void; updateMessage: (message: Message) => void; clearMessages: () => void; - removeMessages: (ids: number[]) => Promise; + removeMessages: (ids: number[]) => void; }; diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts index 9c2735a35..c8391b211 100644 --- a/src/frontend/src/utils/storeUtils.ts +++ b/src/frontend/src/utils/storeUtils.ts @@ -21,8 +21,16 @@ export default function cloneFLowWithParent( } export function getInputsAndOutputs(nodes: Node[]) { - let inputs: { type: string; id: string; displayName: string }[] = []; - let outputs: { type: string; id: string; displayName: string }[] = []; + let inputs: { + type: string; + id: string; + displayName: string; + }[] = []; + let outputs: { + type: string; + id: string; + displayName: string; + }[] = []; nodes.forEach((node) => { const nodeData: NodeDataType = node.data as NodeDataType; if (isOutputNode(nodeData)) {