From 38b26cac0e138e2eae59c6fa9571b777a7acbad3 Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Sat, 3 May 2025 19:29:57 +0200 Subject: [PATCH] Improve locked files detection with smarter pattern matching and prevent AI from attempting to modify locked files --- app/components/chat/Chat.client.tsx | 47 +++++++++++++- app/lib/hooks/useLockedFilesChecker.ts | 87 +++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 4f2b864c..e5bfd123 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -312,10 +312,51 @@ export const ChatImpl = memo( } // Check if the message is trying to modify locked files or folders - const { modifiedPrompt, hasLockedItems } = checkForLockedItems(messageContent); + const { hasLockedItems, lockedFiles, lockedFolders } = checkForLockedItems(messageContent); - // Use the modified prompt that includes warnings about locked files/folders - const finalMessageContent = hasLockedItems ? modifiedPrompt : messageContent; + // If we have locked items, we'll handle this specially + if (hasLockedItems) { + // Create a list of locked items for display + const lockedItems = [ + ...lockedFiles.map((f) => `File: ${f.path} (${f.lockMode} lock)`), + ...lockedFolders.map((f) => `Folder: ${f.path} (${f.lockMode} lock)`), + ].join('\n- '); + + // Add a user message to the chat + const userMessage: Message = { + id: Date.now().toString(), + role: 'user', + content: messageContent, + }; + + // Add an assistant message explaining the locked files + const assistantMessage: Message = { + id: (Date.now() + 1).toString(), + role: 'assistant', + content: `I've detected that you're asking me to modify locked files or folders. These items are currently locked and cannot be modified:\n\n- ${lockedItems}\n\nPlease unlock these items first if you want me to modify them. You can do this by right-clicking on the file/folder in the file tree and selecting "Unlock".`, + }; + + // Add both messages to the chat + setMessages([...messages, userMessage, assistantMessage]); + + // Clear the input + setInput(''); + + // Show an alert + workbenchStore.actionAlert.set({ + type: 'warning', + title: 'Locked Files/Folders Detected', + description: 'Your request mentions locked files or folders', + content: + 'These items are locked and cannot be modified. Please unlock them first if you want to modify them.', + isLockedFile: true, + }); + + return; + } + + // If no locked items, proceed normally with the original message + const finalMessageContent = messageContent; runAnimation(); diff --git a/app/lib/hooks/useLockedFilesChecker.ts b/app/lib/hooks/useLockedFilesChecker.ts index 9e601cd2..ed27814a 100644 --- a/app/lib/hooks/useLockedFilesChecker.ts +++ b/app/lib/hooks/useLockedFilesChecker.ts @@ -12,6 +12,7 @@ const logger = createScopedLogger('useLockedFilesChecker'); * - Update app/components/file.tsx * - Change the code in src/utils/helper.ts * - Fix the bug in folder/file.js + * - Update the readme */ function extractPotentialFilePaths(prompt: string): string[] { // Common file extensions to look for @@ -59,8 +60,92 @@ function extractPotentialFilePaths(prompt: string): string[] { const folderMatches = [...prompt.matchAll(folderRegex)]; const folderPaths = folderMatches.map((match) => match[2]); + // Look for common file names without extensions + const commonFileNames = [ + { pattern: /\b(readme|read\s*me)\b/gi, path: 'README.md' }, + { pattern: /\b(license)\b/gi, path: 'LICENSE' }, + { pattern: /\b(changelog|change\s*log)\b/gi, path: 'CHANGELOG.md' }, + { pattern: /\b(package\.json)\b/gi, path: 'package.json' }, + { pattern: /\b(tsconfig|ts\s*config)\b/gi, path: 'tsconfig.json' }, + { pattern: /\b(gitignore|git\s*ignore)\b/gi, path: '.gitignore' }, + { pattern: /\b(env|environment|\.env)\b/gi, path: '.env' }, + { pattern: /\b(docker\s*file)\b/gi, path: 'Dockerfile' }, + { pattern: /\b(compose\.ya?ml)\b/gi, path: 'docker-compose.yml' }, + + // Common action verbs that might indicate file modification intent + { pattern: /\b(update|modify|change|edit|fix|create|add to|implement|write|rewrite)\b/gi, path: '' }, + ]; + + const commonFilePaths = commonFileNames.flatMap(({ pattern, path }) => { + // If this is an action verb pattern and it matches, we need to check all files + if (path === '' && prompt.match(pattern)) { + return []; // We'll handle action verbs separately + } + + return prompt.match(pattern) ? [path] : []; + }); + + // Also check for files in the current workbench store + const workbenchFiles = Object.keys(workbenchStore.files.get()); + + // Check if the prompt mentions updating or modifying any of these files + const actionWords = [ + 'update', + 'modify', + 'change', + 'edit', + 'fix', + 'create', + 'add to', + 'implement', + 'write', + 'rewrite', + ]; + + // Check if any action word is in the prompt + const hasActionWord = actionWords.some((action) => new RegExp(`\\b${action}\\b`, 'i').test(prompt)); + + // If we detect an action word, we need to be more cautious and check all important files + let mentionedFiles: string[] = []; + + if (hasActionWord) { + // First, check for specific file mentions + mentionedFiles = workbenchFiles.filter((file) => { + const fileName = file.split('/').pop() || ''; + + // Check if any action word is followed by this filename + return actionWords.some((action) => { + const regex = new RegExp(`${action}\\s+(?:the\\s+)?(?:file\\s+)?['"]?${fileName}['"]?`, 'i'); + return regex.test(prompt); + }); + }); + + /* + * If no specific files are mentioned but action words are present, + * include common important files that are often modified + */ + if (mentionedFiles.length === 0) { + const importantFilePatterns = [ + /README\.md$/i, + /package\.json$/i, + /index\.(js|ts|jsx|tsx)$/i, + /app\.(js|ts|jsx|tsx)$/i, + /main\.(js|ts|jsx|tsx)$/i, + /styles\.(css|scss)$/i, + ]; + + mentionedFiles = workbenchFiles.filter((file) => importantFilePatterns.some((pattern) => pattern.test(file))); + } + } else { + // If no action words, just check for direct file mentions + mentionedFiles = workbenchFiles.filter((file) => { + const fileName = file.split('/').pop() || ''; + return new RegExp(`\\b${fileName}\\b`, 'i').test(prompt); + }); + } + // Combine and remove duplicates - return [...new Set([...filePaths, ...folderPaths])]; + return [...new Set([...filePaths, ...folderPaths, ...commonFilePaths, ...mentionedFiles])]; } /**