diff --git a/src/backend/base/langflow/api/v1/monitor.py b/src/backend/base/langflow/api/v1/monitor.py index a6e7b51f2..22b4a663d 100644 --- a/src/backend/base/langflow/api/v1/monitor.py +++ b/src/backend/base/langflow/api/v1/monitor.py @@ -2,6 +2,7 @@ from typing import List, Optional 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, @@ -67,13 +68,13 @@ async def get_messages( raise HTTPException(status_code=500, detail=str(e)) -@router.delete("/messages", status_code=204) +@router.post("/messages", status_code=204) async def delete_messages( - message_ids: List[int], + message_ids: MessageIds, monitor_service: MonitorService = Depends(get_monitor_service), ): try: - monitor_service.delete_messages(message_ids=message_ids) + monitor_service.delete_messages(message_ids=message_ids.ids) 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 9ccdb0085..12d8db2f2 100644 --- a/src/backend/base/langflow/api/v1/schemas.py +++ b/src/backend/base/langflow/api/v1/schemas.py @@ -321,3 +321,6 @@ class FlowDataRequest(BaseModel): class ConfigResponse(BaseModel): frontend_timeout: int + +class MessageIds(BaseModel): + ids: List[int] \ No newline at end of file diff --git a/src/backend/base/langflow/services/monitor/service.py b/src/backend/base/langflow/services/monitor/service.py index b90806128..ea5e1d9b2 100644 --- a/src/backend/base/langflow/services/monitor/service.py +++ b/src/backend/base/langflow/services/monitor/service.py @@ -108,7 +108,7 @@ class MonitorService(Service): return self.exec_query(query) def delete_messages(self, message_ids: list[int]): - query = f"DELETE FROM messages WHERE id IN ({','.join(str(message_ids))})" + query = f"DELETE FROM messages WHERE index IN ({','.join(map(str, message_ids))})" return self.exec_query(query) diff --git a/src/frontend/src/components/tableComponent/index.tsx b/src/frontend/src/components/tableComponent/index.tsx index b6d4fa109..4114f5ec6 100644 --- a/src/frontend/src/components/tableComponent/index.tsx +++ b/src/frontend/src/components/tableComponent/index.tsx @@ -1,14 +1,14 @@ import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid import { AgGridReact, AgGridReactProps } from "ag-grid-react"; -import { ElementRef, forwardRef, useCallback } from "react"; +import { ElementRef, forwardRef } from "react"; import { DEFAULT_TABLE_ALERT_MSG, DEFAULT_TABLE_ALERT_TITLE, } from "../../constants/constants"; import { useDarkStore } from "../../stores/darkStore"; import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid -import { cn } from "../../utils/utils"; +import { cn, toTitleCase } from "../../utils/utils"; import ForwardedIconComponent from "../genericIconComponent"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; @@ -46,16 +46,21 @@ const TableComponent = forwardRef< ); } + const colDef = props.columnDefs.map((col, index) => { if (props.onSelectionChanged && index === 0) { return { ...col, + headerName: toTitleCase(col.headerName), checkboxSelection: true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, }; } else { - return col; + return { + ...col, + headerName: toTitleCase(col.headerName), + }; } }); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 1cbc1da63..c1d3b24a0 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1026,7 +1026,7 @@ export async function getMessagesTable( id?: string, excludedFields?: string[], params = {}, -): Promise<{ rows: Array; columns: Array }> { +): Promise<{ rows: Array; columns: Array }> { const config = {}; if (id) { config["params"] = { flow_id: id }; @@ -1038,3 +1038,9 @@ export async function getMessagesTable( const columns = extractColumnsFromRows(rows.data, mode, excludedFields); return { rows: rows.data, columns }; } + +export async function deleteMessagesFn(ids: number[]) { + return await api.post(`${BASE_URL_API}monitor/messages`, { + ids, + }); +} diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx new file mode 100644 index 000000000..2347699b2 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx @@ -0,0 +1,48 @@ +import ForwardedIconComponent from "../../../../../../components/genericIconComponent"; +import { Button } from "../../../../../../components/ui/button"; +import { cn } from "../../../../../../utils/utils"; + +type HeaderMessagesComponentProps = { + selectedRows: number[]; + handleRemoveMessages: () => void; +}; +const HeaderMessagesComponent = ({ + selectedRows, + handleRemoveMessages, +}: HeaderMessagesComponentProps) => { + return ( + <> +
+
+

+ Messages + +

+

+ Manage your messages as you like. +

+
+
+ +
+
+ + ); +}; +export default HeaderMessagesComponent; 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 new file mode 100644 index 000000000..97cfc2c82 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-messages-table.tsx @@ -0,0 +1,23 @@ +import { useEffect } from "react"; +import { getMessagesTable } from "../../../../../controllers/API"; + +const useMessagesTable = (setColumns, setRows, setMessages) => { + useEffect(() => { + const fetchData = async () => { + try { + const data = await getMessagesTable("union", undefined, ["index"]); + const { columns, rows } = data; + setColumns(columns.map((col) => ({ ...col, editable: true }))); + setRows(rows); + setMessages(rows); + } catch (error) { + console.error("Error fetching messages:", error); + } + }; + fetchData(); + }, []); + + return null; +}; + +export default useMessagesTable; 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 new file mode 100644 index 000000000..01c4eb446 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx @@ -0,0 +1,40 @@ +import { deleteMessagesFn } from "../../../../../controllers/API"; +import { useMessagesStore } from "../../../../../stores/messagesStore"; + +const useRemoveMessages = ( + setRows, + setSelectedRows, + setSuccessData, + setErrorData, + selectedRows, +) => { + const deleteMessages = useMessagesStore((state) => state.removeMessages); + + 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 + setSelectedRows([]); + + // Set success message + setSuccessData({ + title: "Messages deleted successfully.", + }); + } catch (error) { + // Set error message + setErrorData({ + title: "Error deleting messages.", + }); + } + }; + + return { handleRemoveMessages }; +}; + +export default useRemoveMessages; diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx index 8b606fb3c..658d298ee 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx @@ -1,94 +1,40 @@ -import IconComponent from "../../../../components/genericIconComponent"; -import { Button } from "../../../../components/ui/button"; - import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; -import { useEffect, useState } from "react"; -import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton"; -import Dropdown from "../../../../components/dropdownComponent"; -import ForwardedIconComponent from "../../../../components/genericIconComponent"; +import { useState } from "react"; import TableComponent from "../../../../components/tableComponent"; -import { Badge } from "../../../../components/ui/badge"; import { Card, CardContent } from "../../../../components/ui/card"; -import { - deleteGlobalVariable, - getMessagesTable, -} from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; -import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables"; -import { cn } from "../../../../utils/utils"; +import { useMessagesStore } from "../../../../stores/messagesStore"; +import HeaderMessagesComponent from "./components/headerMessages"; +import useMessagesTable from "./hooks/use-messages-table"; +import useRemoveMessages from "./hooks/use-remove-messages"; export default function MessagesPage() { + const setMessages = useMessagesStore((state) => state.setMessages); + const [columns, setColumns] = useState>([]); const [rows, setRows] = useState([]); - const removeGlobalVariable = useGlobalVariablesStore( - (state) => state.removeGlobalVariable, - ); + const setErrorData = useAlertStore((state) => state.setErrorData); - const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); + const setSuccessData = useAlertStore((state) => state.setSuccessData); - const [selectedRows, setSelectedRows] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); - async function removeVariables() { - const deleteGlobalVariablesPromise = selectedRows.map(async (row) => { - const id = getVariableId(row); - const deleteGlobalVariables = deleteGlobalVariable(id!); - await deleteGlobalVariables; - }); - Promise.all(deleteGlobalVariablesPromise) - .then(() => { - selectedRows.forEach((row) => { - removeGlobalVariable(row); - }); - }) - .catch(() => { - setErrorData({ - title: `Error deleting global variables.`, - }); - }); - } + const { handleRemoveMessages } = useRemoveMessages( + setRows, + setSelectedRows, + setSuccessData, + setErrorData, + selectedRows, + ); - useEffect(() => { - console.log("MessagesPage useEffect"); - getMessagesTable("union", undefined, ["index"]).then((data) => { - const { columns, rows } = data; - console.log(data); - setColumns(columns.map((col) => ({ ...col, editable: true }))); - setRows(rows); - }); - }, []); + useMessagesTable(setColumns, setRows, setMessages); return (
-
-
-

- Messages - -

-

- Manage your messages as you like. -

-
-
- -
-
+
@@ -97,7 +43,7 @@ export default function MessagesPage() { overlayNoRowsTemplate="No data available" onSelectionChanged={(event: SelectionChangedEvent) => { setSelectedRows( - event.api.getSelectedRows().map((row) => row.name), + event.api.getSelectedRows().map((row) => row.index), ); }} rowSelection="multiple" diff --git a/src/frontend/src/stores/messagesStore.ts b/src/frontend/src/stores/messagesStore.ts new file mode 100644 index 000000000..21937f910 --- /dev/null +++ b/src/frontend/src/stores/messagesStore.ts @@ -0,0 +1,43 @@ +import { create } from "zustand"; +import { MessagesStoreType } from "../types/zustand/messages"; + +export const useMessagesStore = create((set, get) => ({ + messages: [], + setMessages: (messages) => { + set(() => ({ messages: messages })); + }, + addMessage: (message) => { + set(() => ({ messages: [...get().messages, message] })); + }, + removeMessage: (message) => { + set(() => ({ + messages: get().messages.filter((msg) => msg.id !== message.id), + })); + }, + updateMessage: (message) => { + set(() => ({ + messages: get().messages.map((msg) => + msg.id === message.id ? message : msg, + ), + })); + }, + clearMessages: () => { + set(() => ({ messages: [] })); + }, + removeMessages: (ids) => { + return new Promise((resolve, reject) => { + try { + set((state) => { + const updatedMessages = state.messages.filter( + (msg) => !ids.includes(msg.index), + ); + get().setMessages(updatedMessages); + resolve(updatedMessages); + return { messages: updatedMessages }; + }); + } catch (error) { + reject(error); + } + }); + }, +})); diff --git a/src/frontend/src/types/messages/index.ts b/src/frontend/src/types/messages/index.ts new file mode 100644 index 000000000..6351479e4 --- /dev/null +++ b/src/frontend/src/types/messages/index.ts @@ -0,0 +1,11 @@ +type Message = { + artifacts: Record; + flow_id: string; + index: number; + message: string; + sender: string; + sender_name: string; + session_id: string; + timestamp: string; + id: string; +}; diff --git a/src/frontend/src/types/zustand/messages/index.ts b/src/frontend/src/types/zustand/messages/index.ts new file mode 100644 index 000000000..30d06229d --- /dev/null +++ b/src/frontend/src/types/zustand/messages/index.ts @@ -0,0 +1,9 @@ +export type MessagesStoreType = { + messages: Message[]; + setMessages: (messages: Message[]) => void; + addMessage: (message: Message) => void; + removeMessage: (message: Message) => void; + updateMessage: (message: Message) => void; + clearMessages: () => void; + removeMessages: (ids: number[]) => Promise; +};