Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: @ syntax for bringing files into chat context #475

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

weilirs
Copy link
Collaborator

@weilirs weilirs commented Nov 4, 2024

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Added @ syntax functionality for referencing files in chat context, enabling users to search and include files in conversations through an autocomplete interface.

  • Potential security risk in searchFiles function as it exposes file paths without proper validation or sanitization in electron/main/filesystem/filesystem.ts
  • File search implementation in searchFiles loads all files into memory before filtering, which could cause performance issues with large directories
  • Regex pattern /@([^@]+?\.md)/g in extractFileReferences is fragile and only handles .md files without validating existence
  • Caret position calculation in getCaretCoordinates creates/removes DOM elements frequently without debouncing
  • Missing error handling for file operations and IPC calls across multiple components

7 file(s) reviewed, 17 comment(s)
Edit PR Review Bot Settings | Greptile

@@ -217,3 +219,18 @@ export function splitDirectoryPathIntoBaseAndRepo(fullPath: string) {

return { localModelPath, repoName }
}

export const searchFiles = async (store: Store<StoreSchema>, searchTerm: string): Promise<string[]> => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Function marked as async but contains no await operations

@@ -217,3 +219,18 @@ export function splitDirectoryPathIntoBaseAndRepo(fullPath: string) {

return { localModelPath, repoName }
}

export const searchFiles = async (store: Store<StoreSchema>, searchTerm: string): Promise<string[]> => {
const vaultDirectory = store.get(StoreKeys.DirectoryFromPreviousSession)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Using DirectoryFromPreviousSession instead of current window's vault directory could cause inconsistencies

Comment on lines 229 to 233
const allFiles = GetFilesInfoList(vaultDirectory)

const searchTermLower = searchTerm.toLowerCase()

const matchingFiles = allFiles.filter((file) => file.name.toLowerCase().startsWith(searchTermLower))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Loading all files into memory before filtering could be inefficient for large vaults. Consider using a streaming/iterative approach

Comment on lines +225 to +227
if (!vaultDirectory || typeof vaultDirectory !== 'string') {
throw new Error('No valid vault directory found')
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Error handling could provide more context about why the vault directory is invalid

Comment on lines +198 to +200
ipcMain.handle('search-files', async (_event, searchTerm: string) => {
return searchFiles(store, searchTerm)
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: No error handling for searchFiles - could throw unhandled exceptions if store is invalid or searchTerm is malformed. Consider wrapping in try/catch.

>
{files.map((file) => (
<div key={file} className="cursor-pointer px-4 py-2 hover:bg-neutral-700" onClick={() => onSelect(file)}>
{file.split('/').pop()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: file.split('/').pop() could return undefined if path ends in /

Comment on lines 176 to 180
const handleFileSelect = (filePath: string) => {
const lastAtIndex = userTextFieldInput.lastIndexOf('@')
const newValue = `${userTextFieldInput.slice(0, lastAtIndex)}@${filePath} ${userTextFieldInput.slice(
lastAtIndex + searchTerm.length + 1,
)}`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Potential XSS vulnerability - filePath needs to be sanitized before being inserted into the text input

Comment on lines 114 to 121
// Create a hidden div with the same styling as textarea
const mirror = document.createElement('div')
mirror.style.cssText = window.getComputedStyle(element).cssText
mirror.style.height = 'auto'
mirror.style.position = 'absolute'
mirror.style.visibility = 'hidden'
mirror.style.whiteSpace = 'pre-wrap'
document.body.appendChild(mirror)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Memory leak risk - mirror element needs cleanup if component unmounts while calculating coordinates

Comment on lines +20 to +21
const regex = /@([^@]+?\.md)/g
const matches = message.match(regex)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: regex pattern only matches .md files and doesn't handle spaces or special characters in filenames

Comment on lines 60 to 61
const fileRefs = extractFileReferences(userTextFieldInput || '')
// console.log('fileRefs', fileRefs)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: no validation that referenced files exist before adding to chat context

@milaiwi
Copy link
Collaborator

milaiwi commented Nov 4, 2024

Will take a look tonight

Edit: Delayed due to comment below.

@samlhuillier
Copy link
Collaborator

I'm afraid we'll need to hold off on merging this PR. I'm doing a bit of a revamp of chat to get it working nicely in the sidebar #472 so this feature can only be merged after that's done

@weilirs weilirs marked this pull request as draft November 5, 2024 01:54
@weilirs
Copy link
Collaborator Author

weilirs commented Nov 5, 2024

I'm afraid we'll need to hold off on merging this PR. I'm doing a bit of a revamp of chat to get it working nicely in the sidebar #472 so this feature can only be merged after that's done

Okay, this PR needs more work as well, just want some opinions from you guys.

@weilirs weilirs marked this pull request as ready for review November 20, 2024 08:59
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

(updates since last review)

This PR continues to evolve the @ syntax feature for file references in chat context, with several new changes and components. Here's what's new:

  • Added FileAutocomplete component in /src/components/Chat/FileAutocomplete.tsx for handling file selection UI with hover previews
  • Implemented ChatSources component in /src/components/Chat/MessageComponents/ChatSources.tsx to display referenced files with preview functionality
  • Added vault path resolution in extractFileReferences function to convert relative paths to absolute paths
  • Introduced analytics tracking for LLM-related actions in DefaultLLMSelector.tsx

The implementation still needs attention in these areas:

  • File path handling in FileAutocomplete doesn't account for different operating systems' path separators
  • Autocomplete positioning logic may break with scrolling/resizing
  • Missing loading/empty states in FileAutocomplete component
  • No debouncing on file search operations which could impact performance

9 file(s) reviewed, 14 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +214 to +215
const matchingFiles = allFiles
.filter((file) => file.name.toLowerCase().startsWith(searchTermLower))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Case-sensitive path handling could cause issues on Windows. Consider using case-insensitive comparison with localeCompare

.filter((file) => file.name.toLowerCase().startsWith(searchTermLower))
.map((file) => ({
absolutePath: file.path,
relativePath: file.path.replace(vaultDirectory, '').replace(/^[/\\]+/, ''),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Path separator handling with replace(/^[/\]+/, '') may not work correctly on Windows. Use path.normalize() instead

Comment on lines +164 to +166
ipcMain.handle('get-vault-path', async () => {
return getVaultPath(store)
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: No error handling for getVaultPath - could expose system errors to renderer process

Comment on lines +95 to +98
searchFiles:
createIPCHandler<(searchTerm: string) => Promise<Array<{ absolutePath: string; relativePath: string }>>>(
'search-files',
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Consider adding input validation/sanitization for searchTerm to prevent path traversal attacks

@@ -92,6 +92,11 @@ const fileSystem = {
deleteFile: createIPCHandler<(filePath: string) => Promise<void>>('delete-file'),
getAllFilenamesInDirectory: createIPCHandler<(dirName: string) => Promise<string[]>>('get-files-in-directory'),
getFiles: createIPCHandler<(filePaths: string[]) => Promise<FileInfoWithContent[]>>('get-files'),
searchFiles:
createIPCHandler<(searchTerm: string) => Promise<Array<{ absolutePath: string; relativePath: string }>>>(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The return type should be more specific - consider creating an interface for the path object structure

Comment on lines +39 to +44
className="absolute z-50 max-h-48 w-64 overflow-y-auto rounded-md border border-neutral-700 bg-background shadow-lg"
style={{
top: 'auto',
bottom: `calc(100% + 10px)`,
left: position.left,
}}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: absolute positioning with fixed width could cause overflow issues on small screens. Consider making width responsive or adding overflow handling

left: position.left,
}}
>
{files.map((file) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider limiting number of results shown to prevent performance issues with large file lists

Comment on lines +25 to +30
const vaultPath = await window.fileSystem.getVaultPath()
return matches.map((match) => {
const relativePath = match.slice(1) // Remove @ symbol

return `${vaultPath}/${relativePath}`
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: path concatenation with string interpolation is unsafe and may cause path traversal vulnerabilities. Use path.join() instead

@@ -39,14 +54,22 @@ const ChatComponent: React.FC = () => {
return
}

const fileRefs = await extractFileReferences(userTextFieldInput || '')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider adding error handling for when getVaultPath() fails

Comment on lines 30 to 33
const handleDeleteLLM = async (modelName: string) => {
// eslint-disable-next-line no-alert
const confirmDelete = window.confirm(`Are you sure you want to delete the model ${modelName}?`)
if (!confirmDelete) return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using window.confirm is not ideal for modern UX. Consider using a modal dialog component instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants