修改文件监控逻辑以及UI

This commit is contained in:
2025-08-13 22:04:18 +08:00
parent 2faf07827d
commit d7c34d54bd
6 changed files with 513 additions and 17 deletions
+30
View File
@@ -2482,5 +2482,35 @@ export const api = {
console.error("Failed to unwatch directory:", error);
throw error;
}
},
// ============= Claude Project Directory Watching =============
/**
* Starts watching Claude project directory for the given project path
* @param projectPath - The project path to find the corresponding Claude directory
* @returns Promise resolving when watching starts
*/
async watchClaudeProjectDirectory(projectPath: string): Promise<void> {
try {
return await invoke<void>("watch_claude_project_directory", { projectPath });
} catch (error) {
console.error("Failed to watch Claude project directory:", error);
throw error;
}
},
/**
* Stops watching Claude project directory for the given project path
* @param projectPath - The project path to find the corresponding Claude directory
* @returns Promise resolving when watching stops
*/
async unwatchClaudeProjectDirectory(projectPath: string): Promise<void> {
try {
return await invoke<void>("unwatch_claude_project_directory", { projectPath });
} catch (error) {
console.error("Failed to unwatch Claude project directory:", error);
throw error;
}
}
};
+255
View File
@@ -0,0 +1,255 @@
/**
* -
*/
interface FileChangeListener {
id: string;
filePath: string;
callback: (filePath: string, changeType: string) => void;
}
interface FileContentListener {
id: string;
filePath: string;
callback: (filePath: string, newContent: string) => void;
}
class FileSyncManager {
private changeListeners: Map<string, FileChangeListener[]> = new Map();
private contentListeners: Map<string, FileContentListener[]> = new Map();
private static instance: FileSyncManager | null = null;
public static getInstance(): FileSyncManager {
if (!FileSyncManager.instance) {
FileSyncManager.instance = new FileSyncManager();
}
return FileSyncManager.instance;
}
/**
*
* @param id
* @param filePath
* @param callback
*/
public registerChangeListener(
id: string,
filePath: string,
callback: (filePath: string, changeType: string) => void
): void {
const normalizedPath = this.normalizePath(filePath);
if (!this.changeListeners.has(normalizedPath)) {
this.changeListeners.set(normalizedPath, []);
}
const listeners = this.changeListeners.get(normalizedPath)!;
// 移除已存在的相同ID监听器
const existingIndex = listeners.findIndex(l => l.id === id);
if (existingIndex !== -1) {
listeners.splice(existingIndex, 1);
}
// 添加新监听器
listeners.push({ id, filePath: normalizedPath, callback });
console.log(`[FileSyncManager] Registered change listener ${id} for ${normalizedPath}`);
}
/**
*
* @param id
* @param filePath
* @param callback
*/
public registerContentListener(
id: string,
filePath: string,
callback: (filePath: string, newContent: string) => void
): void {
const normalizedPath = this.normalizePath(filePath);
if (!this.contentListeners.has(normalizedPath)) {
this.contentListeners.set(normalizedPath, []);
}
const listeners = this.contentListeners.get(normalizedPath)!;
// 移除已存在的相同ID监听器
const existingIndex = listeners.findIndex(l => l.id === id);
if (existingIndex !== -1) {
listeners.splice(existingIndex, 1);
}
// 添加新监听器
listeners.push({ id, filePath: normalizedPath, callback });
console.log(`[FileSyncManager] Registered content listener ${id} for ${normalizedPath}`);
}
/**
*
* @param id ID
* @param filePath
*/
public unregisterListener(id: string, filePath?: string): void {
if (filePath) {
const normalizedPath = this.normalizePath(filePath);
// 从变化监听器中移除
const changeListeners = this.changeListeners.get(normalizedPath);
if (changeListeners) {
const index = changeListeners.findIndex(l => l.id === id);
if (index !== -1) {
changeListeners.splice(index, 1);
if (changeListeners.length === 0) {
this.changeListeners.delete(normalizedPath);
}
}
}
// 从内容监听器中移除
const contentListeners = this.contentListeners.get(normalizedPath);
if (contentListeners) {
const index = contentListeners.findIndex(l => l.id === id);
if (index !== -1) {
contentListeners.splice(index, 1);
if (contentListeners.length === 0) {
this.contentListeners.delete(normalizedPath);
}
}
}
} else {
// 移除所有该ID的监听器
for (const [path, listeners] of this.changeListeners.entries()) {
const index = listeners.findIndex(l => l.id === id);
if (index !== -1) {
listeners.splice(index, 1);
if (listeners.length === 0) {
this.changeListeners.delete(path);
}
}
}
for (const [path, listeners] of this.contentListeners.entries()) {
const index = listeners.findIndex(l => l.id === id);
if (index !== -1) {
listeners.splice(index, 1);
if (listeners.length === 0) {
this.contentListeners.delete(path);
}
}
}
}
console.log(`[FileSyncManager] Unregistered listener ${id}${filePath ? ` for ${filePath}` : ''}`);
}
/**
*
* @param filePath
* @param changeType
*/
public notifyFileChange(filePath: string, changeType: string): void {
const normalizedPath = this.normalizePath(filePath);
const listeners = this.changeListeners.get(normalizedPath);
if (listeners && listeners.length > 0) {
console.log(`[FileSyncManager] Notifying ${listeners.length} change listeners for ${normalizedPath}`);
listeners.forEach(listener => {
try {
listener.callback(normalizedPath, changeType);
} catch (error) {
console.error(`[FileSyncManager] Error in change listener ${listener.id}:`, error);
}
});
}
}
/**
*
* @param filePath
* @param newContent
*/
public notifyContentUpdate(filePath: string, newContent: string): void {
const normalizedPath = this.normalizePath(filePath);
const listeners = this.contentListeners.get(normalizedPath);
if (listeners && listeners.length > 0) {
console.log(`[FileSyncManager] Notifying ${listeners.length} content listeners for ${normalizedPath}`);
listeners.forEach(listener => {
try {
listener.callback(normalizedPath, newContent);
} catch (error) {
console.error(`[FileSyncManager] Error in content listener ${listener.id}:`, error);
}
});
}
}
/**
*
* @param filePath
* @returns
*/
private normalizePath(filePath: string): string {
return filePath
.replace(/\\/g, '/') // 统一使用正斜杠
.replace(/\/+/g, '/') // 移除重复斜杠
.replace(/\/$/, ''); // 移除结尾斜杠
}
/**
*
* @param watchPath
* @param changePath
* @returns
*/
public pathMatches(watchPath: string, changePath: string): boolean {
const normalizedWatchPath = this.normalizePath(watchPath);
const normalizedChangePath = this.normalizePath(changePath);
// 完全匹配
if (normalizedWatchPath === normalizedChangePath) {
return true;
}
// 检查是否为相对路径匹配
const watchPathParts = normalizedWatchPath.split('/');
const changePathParts = normalizedChangePath.split('/');
// 如果监听路径更短,检查是否为后缀匹配
if (watchPathParts.length <= changePathParts.length) {
const offset = changePathParts.length - watchPathParts.length;
for (let i = 0; i < watchPathParts.length; i++) {
if (watchPathParts[i] !== changePathParts[i + offset]) {
return false;
}
}
return true;
}
return false;
}
/**
*
*/
public getStats(): { changeListeners: number; contentListeners: number; totalFiles: number } {
const changeListenerCount = Array.from(this.changeListeners.values()).reduce((sum, arr) => sum + arr.length, 0);
const contentListenerCount = Array.from(this.contentListeners.values()).reduce((sum, arr) => sum + arr.length, 0);
const totalFiles = new Set([...this.changeListeners.keys(), ...this.contentListeners.keys()]).size;
return {
changeListeners: changeListenerCount,
contentListeners: contentListenerCount,
totalFiles
};
}
}
export const fileSyncManager = FileSyncManager.getInstance();
export default fileSyncManager;