mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Add early detection of locked files/folders in user prompts
This commit is contained in:
parent
fbba182d22
commit
b42975b531
@ -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',
|
||||
|
@ -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';
|
||||
|
163
app/lib/hooks/useLockedFilesChecker.ts
Normal file
163
app/lib/hooks/useLockedFilesChecker.ts
Normal 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,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user