Merge SessionManagement into SessionManagement

This commit is contained in:
igorrCarvalho 2024-06-03 19:28:44 -03:00
commit cd36ff8ad7
17 changed files with 179 additions and 73 deletions

View file

@ -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))

View file

@ -321,6 +321,3 @@ class FlowDataRequest(BaseModel):
class ConfigResponse(BaseModel):
frontend_timeout: int
class MessageIds(BaseModel):
ids: List[int]

View file

@ -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)

View file

@ -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;

View file

@ -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;
}

View file

@ -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<Array<string>> {
const config = {};
if (id) {
config["params"] = { flow_id: id };
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const sessions = new Set<string>();
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);
}

View file

@ -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,

View file

@ -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<string[]>([]);
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 && (
<TabsTrigger value={"2"}>Outputs</TabsTrigger>
)}
{haveChat && (
<TabsTrigger value={"3"}>History</TabsTrigger>
)}
</TabsList>
</div>

View file

@ -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);

View file

@ -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.",
});

View file

@ -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;

View file

@ -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<Array<ColDef | ColGroupDef>>([]);
const [rows, setRows] = useState<any>([]);
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<number[]>([]);
const { handleRemoveMessages } = useRemoveMessages(
setRows,
setSelectedRows,
setSuccessData,
setErrorData,
selectedRows,
);
useMessagesTable(setColumns, setRows, setMessages);
const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData);
useMessagesTable(setColumns);
function handleUpdateMessage(event: CellEditRequestEvent<any, string>) {
const newValue = event.newValue;
const field = event.column.getColId();
const row = event.data;
const data = {
...row,
[field]: newValue,
};
handleUpdate(data);
}
return (
<div className="flex h-full w-full flex-col justify-between gap-6">
@ -40,6 +56,10 @@ export default function MessagesPage() {
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
<CardContent className="h-full">
<TableComponent
readOnlyEdit
onCellEditRequest={(event) => {
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}
/>
</CardContent>
</Card>

View file

@ -17,7 +17,7 @@ export const useMessagesStore = create<MessagesStoreType>((set, get) => ({
updateMessage: (message) => {
set(() => ({
messages: get().messages.map((msg) =>
msg.id === message.id ? message : msg,
msg.index === message.index ? message : msg,
),
}));
},

View file

@ -9,3 +9,5 @@ type Message = {
timestamp: string;
id: string;
};
export type { Message };

View file

@ -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;

View file

@ -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<Message[]>;
removeMessages: (ids: number[]) => void;
};

View file

@ -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)) {