mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
- Updated the development script to include a build step before launching the app. - Refactored the registration of quick ask and autocomplete functionalities to be asynchronous, ensuring proper initialization. - Introduced IPC channels for getting and setting keyboard shortcuts, allowing users to customize their experience. - Enhanced the platform module to support better interaction with the Electron API for clipboard operations. - Improved the user interface for managing keyboard shortcuts in the settings dialog, providing a more intuitive experience.
151 lines
4.3 KiB
TypeScript
151 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { useElectronAPI } from "@/hooks/use-platform";
|
|
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
|
|
|
interface FileChangedEvent {
|
|
id: string;
|
|
rootFolderId: number | null;
|
|
searchSpaceId: number;
|
|
folderPath: string;
|
|
folderName: string;
|
|
relativePath: string;
|
|
fullPath: string;
|
|
action: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
const DEBOUNCE_MS = 2000;
|
|
const MAX_WAIT_MS = 10_000;
|
|
const MAX_BATCH_SIZE = 50;
|
|
|
|
interface BatchItem {
|
|
folderPath: string;
|
|
folderName: string;
|
|
searchSpaceId: number;
|
|
rootFolderId: number | null;
|
|
filePaths: string[];
|
|
ackIds: string[];
|
|
}
|
|
|
|
export function useFolderSync() {
|
|
const electronAPI = useElectronAPI();
|
|
const queueRef = useRef<BatchItem[]>([]);
|
|
const processingRef = useRef(false);
|
|
const debounceTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
const pendingByFolder = useRef<Map<string, BatchItem>>(new Map());
|
|
const firstEventTime = useRef<Map<string, number>>(new Map());
|
|
const isMountedRef = useRef(false);
|
|
|
|
async function processQueue() {
|
|
if (processingRef.current) return;
|
|
processingRef.current = true;
|
|
while (queueRef.current.length > 0) {
|
|
const batch = queueRef.current.shift()!;
|
|
try {
|
|
await documentsApiService.folderIndexFiles(batch.searchSpaceId, {
|
|
folder_path: batch.folderPath,
|
|
folder_name: batch.folderName,
|
|
search_space_id: batch.searchSpaceId,
|
|
target_file_paths: batch.filePaths,
|
|
root_folder_id: batch.rootFolderId,
|
|
});
|
|
if (electronAPI?.acknowledgeFileEvents && batch.ackIds.length > 0) {
|
|
await electronAPI.acknowledgeFileEvents(batch.ackIds);
|
|
}
|
|
} catch (err) {
|
|
console.error("[FolderSync] Failed to trigger batch re-index:", err);
|
|
}
|
|
}
|
|
processingRef.current = false;
|
|
}
|
|
|
|
function flushFolder(folderKey: string) {
|
|
debounceTimers.current.delete(folderKey);
|
|
firstEventTime.current.delete(folderKey);
|
|
const pending = pendingByFolder.current.get(folderKey);
|
|
if (!pending) return;
|
|
pendingByFolder.current.delete(folderKey);
|
|
|
|
for (let i = 0; i < pending.filePaths.length; i += MAX_BATCH_SIZE) {
|
|
queueRef.current.push({
|
|
...pending,
|
|
filePaths: pending.filePaths.slice(i, i + MAX_BATCH_SIZE),
|
|
ackIds: i === 0 ? pending.ackIds : [],
|
|
});
|
|
}
|
|
processQueue();
|
|
}
|
|
|
|
function enqueueWithDebounce(event: FileChangedEvent) {
|
|
const folderKey = event.folderPath;
|
|
const existing = pendingByFolder.current.get(folderKey);
|
|
|
|
if (existing) {
|
|
const pathSet = new Set(existing.filePaths);
|
|
pathSet.add(event.fullPath);
|
|
existing.filePaths = Array.from(pathSet);
|
|
if (!existing.ackIds.includes(event.id)) {
|
|
existing.ackIds.push(event.id);
|
|
}
|
|
} else {
|
|
pendingByFolder.current.set(folderKey, {
|
|
folderPath: event.folderPath,
|
|
folderName: event.folderName,
|
|
searchSpaceId: event.searchSpaceId,
|
|
rootFolderId: event.rootFolderId,
|
|
filePaths: [event.fullPath],
|
|
ackIds: [event.id],
|
|
});
|
|
firstEventTime.current.set(folderKey, Date.now());
|
|
}
|
|
|
|
const elapsed = Date.now() - (firstEventTime.current.get(folderKey) ?? Date.now());
|
|
if (elapsed >= MAX_WAIT_MS) {
|
|
const existingTimeout = debounceTimers.current.get(folderKey);
|
|
if (existingTimeout) clearTimeout(existingTimeout);
|
|
flushFolder(folderKey);
|
|
return;
|
|
}
|
|
|
|
const existingTimeout = debounceTimers.current.get(folderKey);
|
|
if (existingTimeout) clearTimeout(existingTimeout);
|
|
|
|
const timeout = setTimeout(() => flushFolder(folderKey), DEBOUNCE_MS);
|
|
debounceTimers.current.set(folderKey, timeout);
|
|
}
|
|
|
|
useEffect(() => {
|
|
isMountedRef.current = true;
|
|
if (!electronAPI?.onFileChanged) {
|
|
return () => {
|
|
isMountedRef.current = false;
|
|
};
|
|
}
|
|
|
|
electronAPI.signalRendererReady?.();
|
|
|
|
void electronAPI.getPendingFileEvents?.().then((pendingEvents) => {
|
|
if (!isMountedRef.current || !pendingEvents?.length) return;
|
|
for (const event of pendingEvents) {
|
|
enqueueWithDebounce(event);
|
|
}
|
|
});
|
|
|
|
const cleanup = electronAPI.onFileChanged((event: FileChangedEvent) => {
|
|
enqueueWithDebounce(event);
|
|
});
|
|
|
|
return () => {
|
|
isMountedRef.current = false;
|
|
cleanup();
|
|
for (const timeout of debounceTimers.current.values()) {
|
|
clearTimeout(timeout);
|
|
}
|
|
debounceTimers.current.clear();
|
|
pendingByFolder.current.clear();
|
|
firstEventTime.current.clear();
|
|
};
|
|
}, [electronAPI]);
|
|
}
|