Add early detection of locked files/folders in user prompts

This commit is contained in:
Stijnus 2025-05-03 19:10:43 +02:00
parent fbba182d22
commit b42975b531
3 changed files with 177 additions and 6 deletions

View File

@ -8,7 +8,7 @@ import { useChat } from 'ai/react';
import { useAnimate } from 'framer-motion';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { cssTransition, toast, ToastContainer } from 'react-toastify';
import { useMessageParser, usePromptEnhancer, useShortcuts } from '~/lib/hooks';
import { useMessageParser, usePromptEnhancer, useShortcuts, useLockedFilesChecker } from '~/lib/hooks';
import { description, useChatHistory } from '~/lib/persistence';
import { chatStore } from '~/lib/stores/chat';
import { workbenchStore } from '~/lib/stores/workbench';
@ -232,6 +232,7 @@ export const ChatImpl = memo(
const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
const { parsedMessages, parseMessages } = useMessageParser();
const { checkForLockedItems } = useLockedFilesChecker();
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
@ -310,6 +311,12 @@ export const ChatImpl = memo(
return;
}
// Check if the message is trying to modify locked files or folders
const { modifiedPrompt, hasLockedItems } = checkForLockedItems(messageContent);
// Use the modified prompt that includes warnings about locked files/folders
const finalMessageContent = hasLockedItems ? modifiedPrompt : messageContent;
runAnimation();
if (!chatStarted) {
@ -317,7 +324,7 @@ export const ChatImpl = memo(
if (autoSelectTemplate) {
const { template, title } = await selectStarterTemplate({
message: messageContent,
message: finalMessageContent,
model,
provider,
});
@ -342,7 +349,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${messageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -387,7 +394,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${messageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -426,7 +433,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userUpdateArtifact}${messageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userUpdateArtifact}${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -442,7 +449,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${messageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',

View File

@ -3,6 +3,7 @@ export * from './usePromptEnhancer';
export * from './useShortcuts';
export * from './StickToBottom';
export * from './useEditChatDescription';
export * from './useLockedFilesChecker';
export { default } from './useViewport';
export { useUpdateCheck } from './useUpdateCheck';
export { useFeatures } from './useFeatures';

View File

@ -0,0 +1,163 @@
import { useState } from 'react';
import { createScopedLogger } from '~/utils/logger';
import { isFileLocked, isFolderLocked, getCurrentChatId } from '~/utils/fileLocks';
import { workbenchStore } from '~/lib/stores/workbench';
const logger = createScopedLogger('useLockedFilesChecker');
/**
* Extract file paths from a user prompt
* This is a simple heuristic that looks for patterns like:
* - Modify file.js
* - Update app/components/file.tsx
* - Change the code in src/utils/helper.ts
* - Fix the bug in folder/file.js
*/
function extractPotentialFilePaths(prompt: string): string[] {
// Common file extensions to look for
const fileExtensions = [
'js',
'jsx',
'ts',
'tsx',
'css',
'scss',
'html',
'json',
'md',
'py',
'rb',
'php',
'java',
'c',
'cpp',
'h',
'cs',
'go',
'rs',
'swift',
'kt',
'sh',
'yaml',
'yml',
'toml',
'xml',
'sql',
'graphql',
];
// Create a regex pattern that matches file paths with the extensions
const extensionPattern = fileExtensions.join('|');
const filePathRegex = new RegExp(`\\b([\\w\\-./]+\\.(${extensionPattern}))\\b`, 'g');
// Find all matches
const matches = [...prompt.matchAll(filePathRegex)];
const filePaths = matches.map((match) => match[1]);
// Also look for folder paths (patterns like "in the folder/directory X")
const folderRegex = /\b(folder|directory|dir)\s+['"]?([\/\w\-_.]+)['"]?/gi;
const folderMatches = [...prompt.matchAll(folderRegex)];
const folderPaths = folderMatches.map((match) => match[2]);
// Combine and remove duplicates
return [...new Set([...filePaths, ...folderPaths])];
}
/**
* Hook to check if a user's prompt is trying to modify locked files or folders
*/
export function useLockedFilesChecker() {
const [lockedItems, setLockedItems] = useState<{
files: { path: string; lockMode: string }[];
folders: { path: string; lockMode: string }[];
}>({ files: [], folders: [] });
/**
* Check if a prompt is trying to modify locked files or folders
* @param prompt The user's prompt
* @returns An object with the modified prompt and whether any locked items were found
*/
const checkForLockedItems = (prompt: string) => {
const potentialPaths = extractPotentialFilePaths(prompt);
const currentChatId = getCurrentChatId();
const lockedFiles: { path: string; lockMode: string }[] = [];
const lockedFolders: { path: string; lockMode: string }[] = [];
// Check each potential path
potentialPaths.forEach((path) => {
// Check if it's a file
const fileCheck = isFileLocked(path, currentChatId);
if (fileCheck.locked) {
lockedFiles.push({
path,
lockMode: fileCheck.lockMode || 'full',
});
logger.info(`Detected locked file in prompt: ${path}`);
}
// Check if it's a folder
const folderCheck = isFolderLocked(path, currentChatId);
if (folderCheck.locked) {
lockedFolders.push({
path,
lockMode: folderCheck.lockMode || 'full',
});
logger.info(`Detected locked folder in prompt: ${path}`);
}
});
setLockedItems({ files: lockedFiles, folders: lockedFolders });
// If we found locked items, modify the prompt to warn the AI
let modifiedPrompt = prompt;
if (lockedFiles.length > 0 || lockedFolders.length > 0) {
// Create a warning message for the AI
let warningMessage = '\n\n[IMPORTANT: The following items are locked and cannot be modified:';
if (lockedFiles.length > 0) {
warningMessage += '\nLocked files:';
lockedFiles.forEach((file) => {
warningMessage += `\n- ${file.path} (${file.lockMode} lock)`;
});
}
if (lockedFolders.length > 0) {
warningMessage += '\nLocked folders:';
lockedFolders.forEach((folder) => {
warningMessage += `\n- ${folder.path} (${folder.lockMode} lock)`;
});
}
warningMessage +=
'\nPlease do not attempt to modify these items. If modifications to these items are necessary, please inform the user that they need to unlock them first.]\n\n';
// Add the warning to the beginning of the prompt
modifiedPrompt = warningMessage + prompt;
// Also show an alert to the user
workbenchStore.actionAlert.set({
type: 'warning',
title: 'Locked Files/Folders Detected',
description: 'Your request mentions locked files or folders',
content:
'The AI has been instructed not to modify these locked items. If you need to modify them, please unlock them first.',
isLockedFile: true,
});
}
return {
modifiedPrompt,
hasLockedItems: lockedFiles.length > 0 || lockedFolders.length > 0,
lockedFiles,
lockedFolders,
};
};
return {
lockedItems,
checkForLockedItems,
};
}