Merge SessionManagement into SessionManagement
This commit is contained in:
commit
cd36ff8ad7
17 changed files with 179 additions and 73 deletions
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -321,6 +321,3 @@ class FlowDataRequest(BaseModel):
|
|||
|
||||
class ConfigResponse(BaseModel):
|
||||
frontend_timeout: int
|
||||
|
||||
class MessageIds(BaseModel):
|
||||
ids: List[int]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ type Message = {
|
|||
timestamp: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type { Message };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue