Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function ChatHeader({
onMessageLogs={onMessageLogs}
onClearChat={handleClearChat}
onDelete={handleDeleteSessionInternal}
showRename={!isDefaultSession}
showClearChat={isDefaultSession}
showDelete={!isDefaultSession}
side="bottom"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ChatSidebarProps {
currentSessionId?: string;
onDeleteSession?: (sessionId: string) => void;
onOpenLogs?: (sessionId: string) => void;
renameLocalSession?: (oldSessionId: string, newSessionId: string) => void;
}

export function ChatSidebar({
Expand All @@ -22,9 +23,13 @@ export function ChatSidebar({
currentSessionId,
onDeleteSession,
onOpenLogs,
renameLocalSession,
}: ChatSidebarProps) {
const currentFlowId = useGetFlowId();
const { handleDelete } = useEditSessionInfo({ flowId: currentFlowId });
const { handleDelete, handleRename } = useEditSessionInfo({
flowId: currentFlowId,
renameLocalSession,
});

const sessionIds = useMemo(() => sessions, [sessions]);

Expand Down Expand Up @@ -81,6 +86,7 @@ export function ChatSidebar({
isVisible={visibleSession === session}
updateVisibleSession={handleSessionClick}
inspectSession={onOpenLogs}
handleRename={handleRename}
setActiveSession={() => {
// TODO: Implement active session
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface SessionMoreMenuProps {
onDelete: () => void;
onClearChat?: () => void;
showMessageLogs?: boolean;
showRename?: boolean;
showDelete?: boolean;
showClearChat?: boolean;
// Positioning props
Expand All @@ -36,6 +37,7 @@ export function SessionMoreMenu({
onDelete,
onClearChat,
showMessageLogs = true,
showRename = true,
showDelete = true,
showClearChat = false,
side = "bottom",
Expand Down Expand Up @@ -102,15 +104,17 @@ export function SessionMoreMenu({
sideOffset={sideOffset}
className={cn("p-0", contentClassName)}
>
<SelectItem value="rename" className="session-more-menu-item">
<div className="flex items-center">
<ForwardedIconComponent
name="SquarePen"
className="mr-2 h-4 w-4"
/>
Rename
</div>
</SelectItem>
{showRename && (
<SelectItem value="rename" className="session-more-menu-item">
<div className="flex items-center">
<ForwardedIconComponent
name="SquarePen"
className="mr-2 h-4 w-4"
/>
Rename
</div>
</SelectItem>
)}
{showMessageLogs && (
<SelectItem value="messageLogs" className="session-more-menu-item">
<div className="flex items-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface SessionSelectorProps {
setSelectedView?: (view: { type: string; id: string } | undefined) => void;
playgroundPage?: boolean;
setActiveSession?: (session: string) => void;
handleRename?: (oldSessionId: string, newSessionId: string) => Promise<void>;
}

export function SessionSelector({
Expand All @@ -32,6 +33,7 @@ export function SessionSelector({
setSelectedView,
playgroundPage = false,
setActiveSession,
handleRename,
}: SessionSelectorProps) {
const [isEditing, setIsEditing] = useState(false);
const { mutate: updateSessionName } = useUpdateSessionName();
Expand All @@ -43,29 +45,47 @@ export function SessionSelector({
setIsEditing(true);
};

const handleRenameSave = (newSessionId: string) => {
const handleRenameSave = async (newSessionId: string) => {
setIsEditing(false);
const trimmed = newSessionId.trim();
if (!trimmed || trimmed === session) return;
updateSessionName(
{ old_session_id: session, new_session_id: trimmed },
{
onSuccess: () => {
if (isVisible) {

// Use handleRename if provided (from sidebar), otherwise use mutation directly (from header)
if (handleRename) {
await handleRename(session, trimmed);
updateVisibleSession(trimmed);
if (
selectedView?.type === "Session" &&
selectedView?.id === session &&
setSelectedView
) {
setSelectedView({ type: "Session", id: trimmed });
}
} else {
// Wait for the mutation to complete before updating visible session
await updateSessionName(
{ old_session_id: session, new_session_id: trimmed },
{
onSuccess: () => {
// Update visible session after rename is complete
updateVisibleSession(trimmed);
}
if (
selectedView?.type === "Session" &&
selectedView?.id === session &&
setSelectedView
) {
setSelectedView({ type: "Session", id: trimmed });
}
if (
selectedView?.type === "Session" &&
selectedView?.id === session &&
setSelectedView
) {
setSelectedView({ type: "Session", id: trimmed });
}
},
},
},
);
);
}
};

// Default session (flowId) cannot be renamed or deleted
const isDefaultSession = session === currentFlowId;
const canModifySession = !isDefaultSession;

return (
<div
data-testid="session-selector"
Expand Down Expand Up @@ -100,7 +120,7 @@ export function SessionSelector({
<ShadTooltip styleClasses="z-50" content={session}>
<div className="relative w-full overflow-hidden">
<span className="w-full truncate bg-transparent text-mmd">
{session === currentFlowId ? "Default Session" : session}
{isDefaultSession ? "Default Session" : session}
</span>
</div>
</ShadTooltip>
Expand All @@ -111,7 +131,8 @@ export function SessionSelector({
onRename={handleEditClick}
onMessageLogs={() => inspectSession?.(session)}
onDelete={() => deleteSession(session)}
showDelete={session !== currentFlowId}
showRename={canModifySession}
showDelete={canModifySession}
side="bottom"
align="end"
sideOffset={4}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import { useGetSessionsFromFlowQuery } from "@/controllers/API/queries/messages/
import { useUpdateSessionName } from "@/controllers/API/queries/messages/use-rename-session";
import { usePlaygroundStore } from "@/stores/playgroundStore";

const LOCAL_SESSIONS_STORAGE_KEY = (flowId: string) =>
`langflow_local_sessions_${flowId}`;

export const useEditSessionInfo = ({
flowId,
dbSessions: providedDbSessions,
renameLocalSession,
}: {
flowId?: string;
dbSessions?: string[];
renameLocalSession?: (oldSessionId: string, newSessionId: string) => void;
}) => {
const setSelectedSession = usePlaygroundStore(
(state) => state.setSelectedSession,
Expand Down Expand Up @@ -41,12 +46,18 @@ export const useEditSessionInfo = ({
};

const handleRename = async (sessionId: string, newSessionId: string) => {
if (dbSessions.includes(sessionId)) {
await updateSessionName({
old_session_id: sessionId,
new_session_id: newSessionId,
});
// Update session name via API or sessionStorage
await updateSessionName({
old_session_id: sessionId,
new_session_id: newSessionId,
});

// Update local sessions list using the provided function
if (renameLocalSession) {
renameLocalSession(sessionId, newSessionId);
}

// Update selected session if the renamed session is currently selected
if (flowId && sessionId === selectedSession) {
setSelectedSession(newSessionId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface UseGetAddSessionsProps {
type UseGetAddSessionsReturnType = (props: UseGetAddSessionsProps) => {
addNewSession: (allSessions?: string[]) => string;
removeLocalSession: (sessionId: string) => void;
renameLocalSession: (oldSessionId: string, newSessionId: string) => void;
sessions: string[];
fetchedSessions: string[];
};
Expand Down Expand Up @@ -138,11 +139,22 @@ export const useGetAddSessions: UseGetAddSessionsReturnType = ({
});
};

const renameLocalSession = (oldSessionId: string, newSessionId: string) => {
setLocalSessions((prev) => {
const updated = new Set(prev);
// Remove old session name and add new one
const hadOld = updated.delete(oldSessionId);
updated.add(newSessionId);
return updated;
});
};

const stableSessions = useMemo(() => [...sessions], [sessions]);

return {
addNewSession,
removeLocalSession,
renameLocalSession,
sessions: stableSessions,
fetchedSessions,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export const useChatHistory = (visibleSession: string | null) => {
queryFn: () => {
// Return cached data immediately - this makes the query active and reactive
// We're using useQuery purely as a subscription mechanism, not for fetching
return queryClient.getQueryData<Message[]>(sessionCacheKey) || [];
const cachedData =
queryClient.getQueryData<Message[]>(sessionCacheKey) || [];
return cachedData;
},
staleTime: Infinity, // Never refetch - updates come from setQueryData, not server
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes (allows cleanup of old sessions)
Expand Down Expand Up @@ -69,12 +71,13 @@ export const useChatHistory = (visibleSession: string | null) => {
// In the default session, we show messages that have the same session_id as the flow_id
// OR messages that have NO session_id (legacy behavior)
if (visibleSession === currentFlowId) {
return (
const matches =
isCurrentFlow &&
(message.session_id === visibleSession || !message.session_id)
);
(message.session_id === visibleSession || !message.session_id);
return matches;
}
return isCurrentFlow && message.session_id === visibleSession;
const matches = isCurrentFlow && message.session_id === visibleSession;
return matches;
})
.map((message: Message) => {
let files = message.files;
Expand Down Expand Up @@ -111,7 +114,8 @@ export const useChatHistory = (visibleSession: string | null) => {
};
});

return [...filteredMessages].sort(sortSenderMessages);
const sorted = [...filteredMessages].sort(sortSenderMessages);
return sorted;
}, [messages, visibleSession, currentFlowId]);

return chatHistory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ export const useSendMessage = ({ sessionId }: UseSendMessageProps = {}) => {
}): Promise<void> => {
// Resolve session: sessionId prop > selectedSession > flowId (default)
const actualSession = sessionId ?? selectedSession ?? flowId;

console.debug("[useSendMessage] invoked", {
sessionId: actualSession,
chatInputId,
hasFiles: Boolean(files?.length),
});

// Add placeholder user message immediately
addUserMessage({
id: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ export function FlowPageSlidingContainerContent({
);
const [openLogsModal, setOpenLogsModal] = useState(false);

const { sessions, addNewSession, removeLocalSession, fetchedSessions } =
useGetAddSessions({
flowId: currentFlowId,
currentSessionId,
});
const {
sessions,
addNewSession,
removeLocalSession,
renameLocalSession,
fetchedSessions,
} = useGetAddSessions({
flowId: currentFlowId,
currentSessionId,
});
const { handleDelete } = useEditSessionInfo({
flowId: currentFlowId,
dbSessions: fetchedSessions,
renameLocalSession,
});

// Ensure currentFlowId is always first in sessions list
Expand Down Expand Up @@ -157,6 +163,7 @@ export function FlowPageSlidingContainerContent({
currentSessionId={currentSessionId}
onDeleteSession={handleDeleteSession}
onOpenLogs={handleOpenLogs}
renameLocalSession={renameLocalSession}
/>
</div>
</div>
Expand Down
Loading
Loading