Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

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

Open
weilirs wants to merge 6 commits into
reorproject:mainfrom
weilirs:@-syntax
Open

feat: @ syntax for bringing files into chat context#475
weilirs wants to merge 6 commits into
reorproject:mainfrom
weilirs:@-syntax

Conversation

@weilirs

@weilirs weilirs commented Nov 4, 2024

Copy link
Copy Markdown
Collaborator

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

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

Comment thread electron/main/filesystem/filesystem.ts Outdated
return { localModelPath, repoName }
}

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

Copy link
Copy Markdown

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

}

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

Copy link
Copy Markdown

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 thread electron/main/filesystem/filesystem.ts Outdated
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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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 thread src/components/Chat/StartChat.tsx Outdated
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
Copy Markdown

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 thread src/components/Chat/StartChat.tsx Outdated
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
Copy Markdown

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
Copy Markdown

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 thread src/components/Chat/index.tsx Outdated
Comment on lines +60 to +61
const fileRefs = extractFileReferences(userTextFieldInput || '')
// console.log('fileRefs', fileRefs)

Copy link
Copy Markdown

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

milaiwi commented Nov 4, 2024

Copy link
Copy Markdown
Collaborator

Will take a look tonight

Edit: Delayed due to comment below.

@samlhuillier

Copy link
Copy Markdown
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

weilirs commented Nov 5, 2024

Copy link
Copy Markdown
Collaborator Author

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

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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 thread electron/preload/index.ts
Comment on lines +95 to +98
searchFiles:
createIPCHandler<(searchTerm: string) => Promise<Array<{ absolutePath: string; relativePath: string }>>>(
'search-files',
),

Copy link
Copy Markdown

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

Comment thread electron/preload/index.ts
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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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

return
}

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

Copy link
Copy Markdown

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
Copy Markdown

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 subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants