fix: stop os search modal and sse reconnect loops
This commit is contained in:
+16
-16
@@ -5,11 +5,21 @@
|
||||
|
||||
import { eventBroadcaster } from '@/services/events';
|
||||
|
||||
export async function GET() {
|
||||
export async function GET(request: Request) {
|
||||
const encoder = new TextEncoder();
|
||||
let activeController: ReadableStreamDefaultController<Uint8Array> | null = null;
|
||||
|
||||
const cleanupConnection = () => {
|
||||
if (!activeController) return;
|
||||
console.log('🔌 SSE connection cleanup');
|
||||
eventBroadcaster.removeConnection(activeController);
|
||||
activeController = null;
|
||||
};
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
activeController = controller;
|
||||
|
||||
// Add this connection to the broadcaster
|
||||
console.log('🔌 New SSE connection established');
|
||||
eventBroadcaster.addConnection(controller);
|
||||
@@ -22,25 +32,15 @@ export async function GET() {
|
||||
})}\n\n`;
|
||||
|
||||
controller.enqueue(encoder.encode(initialMessage));
|
||||
|
||||
// Store controller reference for cleanup
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(controller as any)._cleanup = () => {
|
||||
console.log('🔌 SSE connection cleanup');
|
||||
eventBroadcaster.removeConnection(controller);
|
||||
};
|
||||
},
|
||||
|
||||
cancel(controller) {
|
||||
// Clean up when client disconnects
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((controller as any)._cleanup) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(controller as any)._cleanup();
|
||||
}
|
||||
cancel() {
|
||||
cleanupConnection();
|
||||
}
|
||||
});
|
||||
|
||||
request.signal.addEventListener('abort', cleanupConnection, { once: true });
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
@@ -50,4 +50,4 @@ export async function GET() {
|
||||
'Access-Control-Allow-Headers': 'Cache-Control',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ const DEFAULT_SLOT_A: SlotState = {
|
||||
activeTabId: 'views',
|
||||
};
|
||||
|
||||
const EMPTY_SEARCH_FILTERS: Array<{ type: 'context' | 'title' | 'tag'; value: string }> = [];
|
||||
|
||||
function createSingletonState(type: Exclude<PaneType, 'node'>): SlotState {
|
||||
return {
|
||||
tabs: [{ id: createTabId(type), type }],
|
||||
@@ -157,6 +159,8 @@ export default function ThreePanelLayout() {
|
||||
} | null>(null);
|
||||
const [pendingNodes, setPendingNodes] = useState<PendingNode[]>([]);
|
||||
const [dragOverSlot, setDragOverSlot] = useState<SlotId | null>(null);
|
||||
const openNodeIdsRef = useRef<number[]>([]);
|
||||
const handleNodeDeletedRef = useRef<(nodeId: number) => void>(() => {});
|
||||
|
||||
const handleCloseSettings = useCallback(() => {
|
||||
setShowSettings(false);
|
||||
@@ -268,6 +272,10 @@ export default function ThreePanelLayout() {
|
||||
return [...ids];
|
||||
}, [slotStates]);
|
||||
|
||||
useEffect(() => {
|
||||
openNodeIdsRef.current = allOpenNodeIds;
|
||||
}, [allOpenNodeIds]);
|
||||
|
||||
const activeNodeId = useMemo(() => {
|
||||
const activeSlotState = slotStates[activePane];
|
||||
const activeTab = activeSlotState ? getActiveTab(activeSlotState) : undefined;
|
||||
@@ -357,12 +365,12 @@ export default function ThreePanelLayout() {
|
||||
break;
|
||||
case 'NODE_UPDATED':
|
||||
setNodesPanelRefresh((prev) => prev + 1);
|
||||
if (allOpenNodeIds.includes(Number(data.data.nodeId))) {
|
||||
if (openNodeIdsRef.current.includes(Number(data.data.nodeId))) {
|
||||
setFocusPanelRefresh((prev) => prev + 1);
|
||||
}
|
||||
break;
|
||||
case 'NODE_DELETED':
|
||||
handleNodeDeleted(Number(data.data.nodeId));
|
||||
handleNodeDeletedRef.current(Number(data.data.nodeId));
|
||||
setNodesPanelRefresh((prev) => prev + 1);
|
||||
break;
|
||||
case 'EDGE_CREATED':
|
||||
@@ -401,7 +409,7 @@ export default function ThreePanelLayout() {
|
||||
return () => {
|
||||
eventSource?.close();
|
||||
};
|
||||
}, [allOpenNodeIds]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingNodes.length === 0) return;
|
||||
@@ -535,6 +543,10 @@ export default function ThreePanelLayout() {
|
||||
(['A', 'B', 'C'] as SlotId[]).forEach((slot) => closeTabInSlot(slot, tabId));
|
||||
}, [closeTabInSlot]);
|
||||
|
||||
useEffect(() => {
|
||||
handleNodeDeletedRef.current = handleNodeDeleted;
|
||||
}, [handleNodeDeleted]);
|
||||
|
||||
const handleReorderTabs = useCallback((slot: SlotId, fromIndex: number, toIndex: number) => {
|
||||
const state = getSlotState(slot);
|
||||
if (!state || fromIndex === toIndex) return;
|
||||
@@ -981,7 +993,7 @@ export default function ThreePanelLayout() {
|
||||
openNodeFromSlot(nodeId);
|
||||
setShowSearchModal(false);
|
||||
}}
|
||||
existingFilters={[]}
|
||||
existingFilters={EMPTY_SEARCH_FILTERS}
|
||||
/>
|
||||
|
||||
<SettingsModal
|
||||
|
||||
@@ -95,7 +95,8 @@ export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFil
|
||||
// Generate suggestions based on search query
|
||||
useEffect(() => {
|
||||
if (!searchQuery.trim()) {
|
||||
setSuggestions([]);
|
||||
setSuggestions((prev) => (prev.length === 0 ? prev : []));
|
||||
setSelectedIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFil
|
||||
|
||||
const timeoutId = setTimeout(fetchSuggestions, 200);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [searchQuery, existingFilters]);
|
||||
}, [searchQuery]);
|
||||
|
||||
// Handle keyboard navigation
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user