✨ (monitor.py): add MessageIds schema for structured message deletion
♻️ (monitor.py): change delete_messages endpoint to POST for better semantics ♻️ (monitor.py): update delete_messages to use MessageIds schema ✨ (schemas.py): add MessageIds schema for structured message deletion 🐛 (service.py): fix SQL query in delete_messages to use correct column name ✨ (index.tsx): add toTitleCase utility to format column headers ✨ (API/index.ts): add deleteMessagesFn to handle message deletion via API ✨ (headerMessages): add HeaderMessagesComponent for message management UI ✨ (use-messages-table): add useMessagesTable hook to fetch and manage messages ✨ (use-remove-messages): add useRemoveMessages hook to handle message deletion ♻️ (messagesPage): refactor messages page to use new messages store ✨ (messagesStore): create zustand store for managing messages state ✨ (types): add types for messages and zustand messages store
This commit is contained in:
parent
f79289f966
commit
602ebf7b15
12 changed files with 220 additions and 85 deletions
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -321,3 +321,6 @@ class FlowDataRequest(BaseModel):
|
|||
|
||||
class ConfigResponse(BaseModel):
|
||||
frontend_timeout: int
|
||||
|
||||
class MessageIds(BaseModel):
|
||||
ids: List[int]
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1026,7 +1026,7 @@ export async function getMessagesTable(
|
|||
id?: string,
|
||||
excludedFields?: string[],
|
||||
params = {},
|
||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||
Messages
|
||||
<ForwardedIconComponent
|
||||
name="MessagesSquare"
|
||||
className="ml-2 h-5 w-5 text-primary"
|
||||
/>
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Manage your messages as you like.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<Button
|
||||
data-testid="api-key-button-store"
|
||||
variant="primary"
|
||||
className="group px-2"
|
||||
disabled={selectedRows.length === 0}
|
||||
onClick={handleRemoveMessages}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-5 w-5 text-destructive group-disabled:text-primary",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default HeaderMessagesComponent;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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<Array<ColDef | ColGroupDef>>([]);
|
||||
const [rows, setRows] = useState<any>([]);
|
||||
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<string[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<number[]>([]);
|
||||
|
||||
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 (
|
||||
<div className="flex h-full w-full flex-col justify-between gap-6">
|
||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||
Messages
|
||||
<ForwardedIconComponent
|
||||
name="MessagesSquare"
|
||||
className="ml-2 h-5 w-5 text-primary"
|
||||
/>
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Manage your messages as you like.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<Button
|
||||
data-testid="api-key-button-store"
|
||||
variant="primary"
|
||||
className="group px-2"
|
||||
disabled={selectedRows.length === 0}
|
||||
onClick={removeVariables}
|
||||
>
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-5 w-5 text-destructive group-disabled:text-primary",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<HeaderMessagesComponent
|
||||
selectedRows={selectedRows}
|
||||
handleRemoveMessages={handleRemoveMessages}
|
||||
/>
|
||||
|
||||
<div className="flex h-full w-full flex-col justify-between pb-8">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
43
src/frontend/src/stores/messagesStore.ts
Normal file
43
src/frontend/src/stores/messagesStore.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { create } from "zustand";
|
||||
import { MessagesStoreType } from "../types/zustand/messages";
|
||||
|
||||
export const useMessagesStore = create<MessagesStoreType>((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);
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
11
src/frontend/src/types/messages/index.ts
Normal file
11
src/frontend/src/types/messages/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
type Message = {
|
||||
artifacts: Record<string, any>;
|
||||
flow_id: string;
|
||||
index: number;
|
||||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
session_id: string;
|
||||
timestamp: string;
|
||||
id: string;
|
||||
};
|
||||
9
src/frontend/src/types/zustand/messages/index.ts
Normal file
9
src/frontend/src/types/zustand/messages/index.ts
Normal file
|
|
@ -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<Message[]>;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue