feat: add sessions endpoint with session management enhancements (#8596)
* 📝 (monitor.py): Add endpoint to get sessions and handle session_id encoding for API requests 📝 (use-get-messages-mutation.ts): Implement a mutation function to fetch messages with query parameters and handle session_id encoding for API requests 📝 (use-get-messages-polling.ts): Ensure proper encoding of session_id for API requests in polling mutation 📝 (use-get-messages.ts): Handle session_id encoding for API requests in messages query 📝 (new-modal.tsx): Implement functions to handle session deletion and proper encoding of session_id for API requests 📝 (utils.ts): Add functions to encode, decode, validate, format, and prepare session IDs for API requests * 📝 (constants.ts): Add SESSIONS constant to API URLs for monitoring sessions 🔧 (use-delete-messages.ts): Add queryClient to UseRequestProcessor to invalidate sessions query ✨ (use-get-sessions-from-flow.ts): Introduce useGetSessionsFromFlowQuery to fetch sessions from flow 🔧 (use-rename-session.ts): Change refetchQueries to invalidateQueries for useGetSessionsFromFlowQuery 🔧 (custom-new-modal.tsx): Update import path for IOModal to playground-modal 🔧 (session-selector.tsx): Add setActiveSession function to handle setting active session 🔧 (sidebar-open-view.tsx): Add setActiveSession function to handle setting active session ♻️ (new-modal.tsx): Refactor IOModal into playground-modal and update functionality ♻️ (playground-modal.tsx): Refactor IOModal to handle playground-specific functionality ⬆️ (flowStore.ts): Add newChatOnPlayground state and setNewChatOnPlayground function ⬆️ (index.ts): Update FlowStoreType to include newChatOnPlayground and setNewChatOnPlayground * 🔧 (pyproject.toml): update testpaths to point to the correct directory for tests ✨ (test_session_endpoint.py): add unit tests for sessions endpoint with flow_id filtering ♻️ (session-selector.tsx): refactor to trim editedSession before setting it ♻️ (sidebar-open-view.tsx): refactor to set visibleSession instead of activeSession * ✨ (use-get-sessions-from-flow.ts): Always include the flow ID as the default session if it's not already present ♻️ (playground-modal.tsx): Refactor setting sessions to include currentFlowId as the default session if not present, and handle visibility of sessions more efficiently * ♻️ (use-get-messages-mutation.ts): remove unused imports and refactor code for better readability and maintainability * ✨ (test_session_endpoint.py): refactor test function names for better clarity and consistency * ✨ (create-new-session-name.ts): add function to generate a new session name based on the current date and time 🔧 (playground-modal.tsx): import createNewSessionName function to dynamically set a new session name when no session is visible * [autofix.ci] apply automated fixes * ✨ (monitor.py): rename get_sessions endpoint to get_message_sessions for clarity and consistency 🔧 (constants.ts): remove unused SESSIONS constant from API URLs 🔧 (use-delete-messages.ts): remove commented out code and unnecessary comments ✨ (use-delete-sessions.ts): add functionality to delete sessions in frontend 🔧 (use-get-sessions-from-flow.ts): update API endpoint for getting sessions to match backend changes 🔧 (playground-modal.tsx): add functionality to delete sessions and associated messages in the UI, update UI optimistically, and handle errors appropriately * [autofix.ci] apply automated fixes * 🐛 (monitor.py): Fix type hinting issue in delete_messages function 📝 (monitor.py): Add comments and improve readability in test_messages_endpoints.py 📝 (session_endpoint.py): Update endpoint paths for consistency and clarity in test_session_endpoint.py * [autofix.ci] apply automated fixes * fix: update SQL statement to use col() for session_id filtering in get_message_sessions function --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
068db12c33
commit
043ba55718
19 changed files with 672 additions and 65 deletions
|
|
@ -12,7 +12,7 @@ export const useDeleteMessages: useMutationFunctionType<
|
|||
undefined,
|
||||
DeleteMessagesParams
|
||||
> = (options?) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
const { mutate, queryClient } = UseRequestProcessor();
|
||||
|
||||
const deleteMessage = async ({ ids }: DeleteMessagesParams): Promise<any> => {
|
||||
const response = await api.delete(`${getURL("MESSAGES")}`, {
|
||||
|
|
@ -26,7 +26,15 @@ export const useDeleteMessages: useMutationFunctionType<
|
|||
DeleteMessagesParams,
|
||||
any,
|
||||
DeleteMessagesParams
|
||||
> = mutate(["useDeleteMessages"], deleteMessage, options);
|
||||
> = mutate(["useDeleteMessages"], deleteMessage, {
|
||||
...options,
|
||||
onSettled: (data, error, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["useGetSessionsFromFlowQuery"],
|
||||
});
|
||||
options?.onSettled?.(data, error, variables, context);
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface DeleteSessionParams {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export const useDeleteSession: useMutationFunctionType<
|
||||
undefined,
|
||||
DeleteSessionParams
|
||||
> = (options?) => {
|
||||
const { mutate, queryClient } = UseRequestProcessor();
|
||||
|
||||
const deleteSession = async ({
|
||||
sessionId,
|
||||
}: DeleteSessionParams): Promise<any> => {
|
||||
const response = await api.delete(
|
||||
`${getURL("MESSAGES")}/session/${sessionId}`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const mutation: UseMutationResult<
|
||||
DeleteSessionParams,
|
||||
any,
|
||||
DeleteSessionParams
|
||||
> = mutate(["useDeleteSession"], deleteSession, {
|
||||
...options,
|
||||
onSettled: (data, error, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["useGetSessionsFromFlowQuery"],
|
||||
});
|
||||
options?.onSettled?.(data, error, variables, context);
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -2,7 +2,10 @@ import { useMessagesStore } from "@/stores/messagesStore";
|
|||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { extractColumnsFromRows } from "../../../../utils/utils";
|
||||
import {
|
||||
extractColumnsFromRows,
|
||||
prepareSessionIdForAPI,
|
||||
} from "../../../../utils/utils";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
|
@ -109,7 +112,14 @@ export const useGetMessagesPollingMutation = (
|
|||
}
|
||||
|
||||
if (params) {
|
||||
config["params"] = { ...config["params"], ...params };
|
||||
// Process params to ensure session_id is properly encoded
|
||||
const processedParams = { ...params } as any;
|
||||
if (processedParams.session_id) {
|
||||
processedParams.session_id = prepareSessionIdForAPI(
|
||||
processedParams.session_id,
|
||||
);
|
||||
}
|
||||
config["params"] = { ...config["params"], ...processedParams };
|
||||
}
|
||||
|
||||
const data = await api.get<any>(`${getURL("MESSAGES")}`, config);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import { useMessagesStore } from "@/stores/messagesStore";
|
|||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import { useQueryFunctionType } from "../../../../types/api";
|
||||
import { extractColumnsFromRows } from "../../../../utils/utils";
|
||||
import {
|
||||
extractColumnsFromRows,
|
||||
prepareSessionIdForAPI,
|
||||
} from "../../../../utils/utils";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
|
@ -33,7 +36,14 @@ export const useGetMessagesQuery: useQueryFunctionType<
|
|||
config["params"] = { flow_id: id };
|
||||
}
|
||||
if (params) {
|
||||
config["params"] = { ...config["params"], ...params };
|
||||
// Process params to ensure session_id is properly encoded
|
||||
const processedParams = { ...params } as any;
|
||||
if (processedParams.session_id) {
|
||||
processedParams.session_id = prepareSessionIdForAPI(
|
||||
processedParams.session_id,
|
||||
);
|
||||
}
|
||||
config["params"] = { ...config["params"], ...processedParams };
|
||||
}
|
||||
if (!isPlaygroundPage) {
|
||||
return await api.get<any>(`${getURL("MESSAGES")}`, config);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import useFlowStore from "@/stores/flowStore";
|
||||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import { useQueryFunctionType } from "../../../../types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface SessionsQueryParams {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface SessionsResponse {
|
||||
sessions: string[];
|
||||
}
|
||||
|
||||
export const useGetSessionsFromFlowQuery: useQueryFunctionType<
|
||||
SessionsQueryParams,
|
||||
SessionsResponse
|
||||
> = ({ id }, options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const getSessionsFn = async (id?: string) => {
|
||||
const isPlaygroundPage = useFlowStore.getState().playgroundPage;
|
||||
const config = {};
|
||||
if (id) {
|
||||
config["params"] = { flow_id: id };
|
||||
}
|
||||
|
||||
if (!isPlaygroundPage) {
|
||||
return await api.get<string[]>(`${getURL("MESSAGES")}/sessions`, config);
|
||||
} else {
|
||||
// For playground mode, get sessions from sessionStorage
|
||||
const data = JSON.parse(window.sessionStorage.getItem(id ?? "") || "[]");
|
||||
// Extract unique session IDs from stored messages
|
||||
const sessionIdsSet = new Set(
|
||||
data.map((msg: any) => msg.session_id).filter(Boolean),
|
||||
);
|
||||
const sessionIds = Array.from(sessionIdsSet);
|
||||
|
||||
// Always include the flow ID as the default session if it's not already present
|
||||
if (id && !sessionIds.includes(id)) {
|
||||
sessionIds.unshift(id);
|
||||
}
|
||||
|
||||
return {
|
||||
data: sessionIds,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const responseFn = async () => {
|
||||
const response = await getSessionsFn(id);
|
||||
return { sessions: response.data };
|
||||
};
|
||||
|
||||
const queryResult = query(
|
||||
["useGetSessionsFromFlowQuery", { id }],
|
||||
responseFn,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
...options,
|
||||
},
|
||||
);
|
||||
|
||||
return queryResult;
|
||||
};
|
||||
|
|
@ -48,10 +48,9 @@ export const useUpdateSessionName: useMutationFunctionType<
|
|||
const mutation: UseMutationResult<Message[], any, UpdateSessionParams> =
|
||||
mutate(["useUpdateSessionName"], updateSessionApi, {
|
||||
...options,
|
||||
onSettled: (data, variables, context) => {
|
||||
// Invalidate and refetch relevant queries
|
||||
queryClient.refetchQueries({
|
||||
queryKey: ["useGetMessagesQuery"],
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["useGetSessionsFromFlowQuery"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import IOModal from "@/modals/IOModal/new-modal";
|
||||
import IOModal from "@/modals/IOModal/playground-modal";
|
||||
import { IOModalPropsType } from "@/types/components";
|
||||
|
||||
export function CustomIOModal({
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export default function SessionSelector({
|
|||
selectedView,
|
||||
setSelectedView,
|
||||
playgroundPage,
|
||||
setActiveSession,
|
||||
}: {
|
||||
deleteSession: (session: string) => void;
|
||||
session: string;
|
||||
|
|
@ -36,6 +37,7 @@ export default function SessionSelector({
|
|||
selectedView?: { type: string; id: string };
|
||||
setSelectedView: (view: { type: string; id: string } | undefined) => void;
|
||||
playgroundPage: boolean;
|
||||
setActiveSession: (session: string) => void;
|
||||
}) {
|
||||
const clientId = useUtilityStore((state) => state.clientId);
|
||||
let realFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
|
|
@ -46,6 +48,9 @@ export default function SessionSelector({
|
|||
const [editedSession, setEditedSession] = useState(session);
|
||||
const { mutate: updateSessionName } = useUpdateSessionName();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const setNewChatOnPlayground = useFlowStore(
|
||||
(state) => state.setNewChatOnPlayground,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setEditedSession(session);
|
||||
|
|
@ -68,13 +73,13 @@ export default function SessionSelector({
|
|||
{
|
||||
onSuccess: () => {
|
||||
if (isVisible) {
|
||||
updateVisibleSession(editedSession);
|
||||
updateVisibleSession(editedSession.trim());
|
||||
}
|
||||
if (
|
||||
selectedView?.type === "Session" &&
|
||||
selectedView?.id === session
|
||||
) {
|
||||
setSelectedView({ type: "Session", id: editedSession });
|
||||
setSelectedView({ type: "Session", id: editedSession.trim() });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
export const createNewSessionName = () => {
|
||||
return `Session ${new Date().toLocaleString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
second: "2-digit",
|
||||
timeZone: "UTC",
|
||||
})}`;
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { useVoiceStore } from "@/stores/voiceStore";
|
||||
import IconComponent from "../../../components/common/genericIconComponent";
|
||||
import { SidebarOpenViewProps } from "../types/sidebar-open-view";
|
||||
|
|
@ -13,11 +14,16 @@ export const SidebarOpenView = ({
|
|||
visibleSession,
|
||||
selectedViewField,
|
||||
playgroundPage,
|
||||
setActiveSession,
|
||||
}: SidebarOpenViewProps) => {
|
||||
const setNewSessionCloseVoiceAssistant = useVoiceStore(
|
||||
(state) => state.setNewSessionCloseVoiceAssistant,
|
||||
);
|
||||
|
||||
const setNewChatOnPlayground = useFlowStore(
|
||||
(state) => state.setNewChatOnPlayground,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col pl-3">
|
||||
|
|
@ -40,6 +46,7 @@ export const SidebarOpenView = ({
|
|||
setvisibleSession(undefined);
|
||||
setSelectedViewField(undefined);
|
||||
setNewSessionCloseVoiceAssistant(true);
|
||||
setNewChatOnPlayground(true);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
@ -78,6 +85,9 @@ export const SidebarOpenView = ({
|
|||
type: "Session",
|
||||
});
|
||||
}}
|
||||
setActiveSession={(session) => {
|
||||
setActiveSession(session);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
//import LangflowLogoColor from "@/assets/LangflowLogocolor.svg?react";
|
||||
import ThemeButtons from "@/components/core/appHeaderComponent/components/ThemeButtons";
|
||||
import { EventDeliveryType } from "@/constants/enums";
|
||||
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
|
||||
import {
|
||||
useDeleteMessages,
|
||||
useGetMessagesQuery,
|
||||
} from "@/controllers/API/queries/messages";
|
||||
import { useDeleteSession } from "@/controllers/API/queries/messages/use-delete-sessions";
|
||||
import { useGetSessionsFromFlowQuery } from "@/controllers/API/queries/messages/use-get-sessions-from-flow";
|
||||
import { ENABLE_PUBLISH } from "@/customization/feature-flags";
|
||||
import { track } from "@/customization/utils/analytics";
|
||||
import { customOpenNewTab } from "@/customization/utils/custom-open-new-tab";
|
||||
import { LangflowButtonRedirectTarget } from "@/customization/utils/urls";
|
||||
import { useUtilityStore } from "@/stores/utilityStore";
|
||||
import { swatchColors } from "@/utils/styleUtils";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import LangflowLogoColor from "../../assets/LangflowLogoColor.svg?react";
|
||||
|
|
@ -27,6 +27,7 @@ import { IOModalPropsType } from "../../types/components";
|
|||
import { cn, getNumberFromString } from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { ChatViewWrapper } from "./components/chat-view-wrapper";
|
||||
import { createNewSessionName } from "./components/chatView/chatInput/components/voice-assistant/helpers/create-new-session-name";
|
||||
import { SelectedViewField } from "./components/selected-view-field";
|
||||
import { SidebarOpenView } from "./components/sidebar-open-view";
|
||||
|
||||
|
|
@ -46,6 +47,13 @@ export default function IOModal({
|
|||
const buildFlow = useFlowStore((state) => state.buildFlow);
|
||||
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
const newChatOnPlayground = useFlowStore(
|
||||
(state) => state.newChatOnPlayground,
|
||||
);
|
||||
const setNewChatOnPlayground = useFlowStore(
|
||||
(state) => state.setNewChatOnPlayground,
|
||||
);
|
||||
|
||||
const { flowIcon, flowId, flowGradient, flowName } = useFlowStore(
|
||||
useShallow((state) => ({
|
||||
flowIcon: state.currentFlow?.icon,
|
||||
|
|
@ -76,12 +84,36 @@ export default function IOModal({
|
|||
: realFlowId;
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||
|
||||
const { mutate: deleteSessionFunction } = useDeleteMessages();
|
||||
const { mutate: deleteMessagesFunction } = useDeleteMessages();
|
||||
const { mutate: deleteSessionFunction } = useDeleteSession();
|
||||
|
||||
const [visibleSession, setvisibleSession] = useState<string | undefined>(
|
||||
currentFlowId,
|
||||
);
|
||||
const PlaygroundTitle = playgroundPage && flowName ? flowName : "Playground";
|
||||
|
||||
const {
|
||||
data: sessionsFromDb,
|
||||
isLoading: sessionsLoading,
|
||||
refetch: refetchSessions,
|
||||
} = useGetSessionsFromFlowQuery(
|
||||
{
|
||||
id: currentFlowId,
|
||||
},
|
||||
{ enabled: open },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionsFromDb && !sessionsLoading) {
|
||||
const sessions = [...sessionsFromDb.sessions];
|
||||
// Always include the currentFlowId as the default session if it's not already present
|
||||
if (!sessions.includes(currentFlowId)) {
|
||||
sessions.unshift(currentFlowId);
|
||||
}
|
||||
setSessions(sessions);
|
||||
}
|
||||
}, [sessionsFromDb, sessionsLoading, currentFlowId]);
|
||||
|
||||
useEffect(() => {
|
||||
setIOModalOpen(open);
|
||||
return () => {
|
||||
|
|
@ -90,25 +122,45 @@ export default function IOModal({
|
|||
}, [open]);
|
||||
|
||||
function handleDeleteSession(session_id: string) {
|
||||
// Update UI optimistically
|
||||
if (visibleSession === session_id) {
|
||||
const remainingSessions = sessions.filter((s) => s !== session_id);
|
||||
if (remainingSessions.length > 0) {
|
||||
setvisibleSession(remainingSessions[0]);
|
||||
} else {
|
||||
setvisibleSession(currentFlowId);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the session (which will delete all associated messages on the backend)
|
||||
deleteSessionFunction(
|
||||
{
|
||||
ids: messages
|
||||
.filter((msg) => msg.session_id === session_id)
|
||||
.map((msg) => msg.id),
|
||||
},
|
||||
{ sessionId: session_id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Remove the session from local state
|
||||
deleteSession(session_id);
|
||||
|
||||
// Remove all messages for this session from local state
|
||||
const messageIdsToRemove = messages
|
||||
.filter((msg) => msg.session_id === session_id)
|
||||
.map((msg) => msg.id);
|
||||
|
||||
if (messageIdsToRemove.length > 0) {
|
||||
removeMessages(messageIdsToRemove);
|
||||
}
|
||||
|
||||
setSuccessData({
|
||||
title: "Session deleted successfully.",
|
||||
});
|
||||
deleteSession(session_id);
|
||||
if (visibleSession === session_id) {
|
||||
setvisibleSession(undefined);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
// Revert optimistic UI update on error
|
||||
if (visibleSession !== session_id) {
|
||||
setvisibleSession(session_id);
|
||||
}
|
||||
|
||||
setErrorData({
|
||||
title: "Error deleting Session.",
|
||||
title: "Error deleting session.",
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -132,27 +184,24 @@ export default function IOModal({
|
|||
>(startView());
|
||||
|
||||
const messages = useMessagesStore((state) => state.messages);
|
||||
const [sessions, setSessions] = useState<string[]>(
|
||||
Array.from(
|
||||
new Set(
|
||||
messages
|
||||
.filter((message) => message.flow_id === currentFlowId)
|
||||
.map((message) => message.session_id),
|
||||
),
|
||||
),
|
||||
);
|
||||
const removeMessages = useMessagesStore((state) => state.removeMessages);
|
||||
const [sessions, setSessions] = useState<string[]>([]);
|
||||
const [sessionId, setSessionId] = useState<string>(currentFlowId);
|
||||
const setCurrentSessionId = useUtilityStore(
|
||||
(state) => state.setCurrentSessionId,
|
||||
);
|
||||
|
||||
const { isFetched: messagesFetched } = useGetMessagesQuery(
|
||||
{
|
||||
mode: "union",
|
||||
id: currentFlowId,
|
||||
},
|
||||
{ enabled: open },
|
||||
);
|
||||
const { isFetched: messagesFetched, refetch: refetchMessages } =
|
||||
useGetMessagesQuery(
|
||||
{
|
||||
mode: "union",
|
||||
id: currentFlowId,
|
||||
params: {
|
||||
session_id: visibleSession,
|
||||
},
|
||||
},
|
||||
{ enabled: open },
|
||||
);
|
||||
|
||||
const chatValue = useUtilityStore((state) => state.chatValueStore);
|
||||
const setChatValue = useUtilityStore((state) => state.setChatValueStore);
|
||||
|
|
@ -185,28 +234,28 @@ export default function IOModal({
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
const sessions = new Set<string>();
|
||||
messages
|
||||
.filter((message) => message.flow_id === currentFlowId)
|
||||
.forEach((row) => {
|
||||
sessions.add(row.session_id);
|
||||
});
|
||||
setSessions((prev) => {
|
||||
if (prev.length < Array.from(sessions).length) {
|
||||
// set the new session as visible
|
||||
setvisibleSession(
|
||||
Array.from(sessions)[Array.from(sessions).length - 1],
|
||||
);
|
||||
}
|
||||
return Array.from(sessions);
|
||||
});
|
||||
if (newChatOnPlayground && !sessionsLoading) {
|
||||
const handleRefetchAndSetSession = async () => {
|
||||
try {
|
||||
const result = await refetchSessions();
|
||||
if (result.data?.sessions && result.data.sessions.length > 0) {
|
||||
setvisibleSession(
|
||||
result.data.sessions[result.data.sessions.length - 1],
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error refetching sessions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
handleRefetchAndSetSession();
|
||||
setNewChatOnPlayground(false);
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visibleSession) {
|
||||
setSessionId(
|
||||
`Session ${new Date().toLocaleString("en-US", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", hour12: false, second: "2-digit", timeZone: "UTC" })}`,
|
||||
);
|
||||
setSessionId(createNewSessionName());
|
||||
setCurrentSessionId(currentFlowId);
|
||||
} else if (visibleSession) {
|
||||
setSessionId(visibleSession);
|
||||
|
|
@ -271,6 +320,35 @@ export default function IOModal({
|
|||
: getNumberFromString(flowGradient ?? flowId ?? "")) %
|
||||
swatchColors.length;
|
||||
|
||||
const setActiveSession = (session: string) => {
|
||||
setvisibleSession((prev) => {
|
||||
if (prev === session) {
|
||||
return undefined;
|
||||
}
|
||||
return session;
|
||||
});
|
||||
};
|
||||
|
||||
const [hasInitialized, setHasInitialized] = useState(false);
|
||||
const prevVisibleSessionRef = useRef<string | undefined>(visibleSession);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitialized) {
|
||||
setHasInitialized(true);
|
||||
prevVisibleSessionRef.current = visibleSession;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
open &&
|
||||
visibleSession &&
|
||||
prevVisibleSessionRef.current !== visibleSession
|
||||
) {
|
||||
refetchMessages();
|
||||
}
|
||||
|
||||
prevVisibleSessionRef.current = visibleSession;
|
||||
}, [visibleSession]);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
open={open}
|
||||
|
|
@ -336,7 +414,7 @@ export default function IOModal({
|
|||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
{sidebarOpen && (
|
||||
{sidebarOpen && !sessionsLoading && (
|
||||
<SidebarOpenView
|
||||
sessions={sessions}
|
||||
setSelectedViewField={setSelectedViewField}
|
||||
|
|
@ -345,6 +423,7 @@ export default function IOModal({
|
|||
visibleSession={visibleSession}
|
||||
selectedViewField={selectedViewField}
|
||||
playgroundPage={!!playgroundPage}
|
||||
setActiveSession={setActiveSession}
|
||||
/>
|
||||
)}
|
||||
{sidebarOpen && showPublishOptions && (
|
||||
|
|
@ -383,7 +462,7 @@ export default function IOModal({
|
|||
</div>
|
||||
)}
|
||||
<div className="flex h-full min-w-96 flex-grow bg-background">
|
||||
{selectedViewField && (
|
||||
{selectedViewField && !sessionsLoading && (
|
||||
<SelectedViewField
|
||||
selectedViewField={selectedViewField}
|
||||
setSelectedViewField={setSelectedViewField}
|
||||
|
|
@ -8,4 +8,5 @@ export type SidebarOpenViewProps = {
|
|||
visibleSession: string | undefined;
|
||||
selectedViewField: { type: string; id: string } | undefined;
|
||||
playgroundPage: boolean;
|
||||
setActiveSession: (session: string) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1061,6 +1061,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
);
|
||||
set({ dismissedNodes: newDismissedNodes });
|
||||
},
|
||||
setNewChatOnPlayground: (newChat: boolean) => {
|
||||
set({ newChatOnPlayground: newChat });
|
||||
},
|
||||
newChatOnPlayground: false,
|
||||
}));
|
||||
|
||||
export default useFlowStore;
|
||||
|
|
|
|||
|
|
@ -285,4 +285,6 @@ export type FlowStoreType = {
|
|||
setCurrentBuildingNodeId: (nodeIds: string[] | undefined) => void;
|
||||
clearEdgesRunningByNodes: () => Promise<void>;
|
||||
updateToolMode: (nodeId: string, toolMode: boolean) => void;
|
||||
newChatOnPlayground: boolean;
|
||||
setNewChatOnPlayground: (newChat: boolean) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -902,3 +902,93 @@ export function getOS() {
|
|||
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a session ID for safe URL transmission
|
||||
* Handles both UUID format and date-time format session IDs
|
||||
* @param {string} session_id - The session ID to encode
|
||||
* @returns {string} The URL-encoded session ID
|
||||
*/
|
||||
export function encodeSessionId(session_id: string): string {
|
||||
if (!session_id) return "";
|
||||
// Use encodeURIComponent to properly encode spaces, commas, colons, etc.
|
||||
return encodeURIComponent(session_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a session ID from URL encoding
|
||||
* @param {string} encoded_session_id - The URL-encoded session ID
|
||||
* @returns {string} The decoded session ID
|
||||
*/
|
||||
export function decodeSessionId(encoded_session_id: string): string {
|
||||
if (!encoded_session_id) return "";
|
||||
try {
|
||||
return decodeURIComponent(encoded_session_id);
|
||||
} catch (error) {
|
||||
console.warn("Failed to decode session ID:", encoded_session_id, error);
|
||||
return encoded_session_id; // Return as-is if decoding fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a string is a valid UUID format
|
||||
* @param {string} str - The string to validate
|
||||
* @returns {boolean} True if the string is a valid UUID format
|
||||
*/
|
||||
export function isUUID(str: string): boolean {
|
||||
const uuidRegex =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a string is a date-time session format
|
||||
* @param {string} str - The string to validate
|
||||
* @returns {boolean} True if the string appears to be a date-time session format
|
||||
*/
|
||||
export function isDateTimeSession(str: string): boolean {
|
||||
// Check for patterns like "Session Jun 16, 15:44:08" or similar
|
||||
const dateTimeSessionRegex =
|
||||
/^Session\s+\w{3}\s+\d{1,2},\s+\d{2}:\d{2}:\d{2}$/;
|
||||
return dateTimeSessionRegex.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and normalizes session IDs for consistent handling
|
||||
* Handles both UUID format and date-time format session IDs
|
||||
* @param {string} session_id - The session ID to format
|
||||
* @returns {string} The formatted session ID
|
||||
*/
|
||||
export function sessionIdFormatted(session_id: string): string {
|
||||
if (!session_id) return "";
|
||||
|
||||
// Decode if it appears to be URL encoded
|
||||
let decodedId = session_id;
|
||||
if (session_id.includes("%") || session_id.includes("+")) {
|
||||
decodedId = decodeSessionId(session_id);
|
||||
}
|
||||
|
||||
// If it's a UUID, return as-is (already in good format)
|
||||
if (isUUID(decodedId)) {
|
||||
return decodedId;
|
||||
}
|
||||
|
||||
// If it's a date-time session, return as-is
|
||||
if (isDateTimeSession(decodedId)) {
|
||||
return decodedId;
|
||||
}
|
||||
|
||||
// For any other format, return as-is but ensure it's properly trimmed
|
||||
return decodedId.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely prepares a session ID for API requests
|
||||
* This function should be used when adding session_id to API parameters
|
||||
* @param {string} session_id - The session ID to prepare
|
||||
* @returns {string} The properly encoded session ID for API use
|
||||
*/
|
||||
export function prepareSessionIdForAPI(session_id: string): string {
|
||||
const formatted = sessionIdFormatted(session_id);
|
||||
return encodeSessionId(formatted);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue