Skip to content

Commit 0c60b6c

Browse files
authored
Merge pull request #1 from duck4i/feat/code-merge
Feat/code merge
2 parents 2327684 + ed69adb commit 0c60b6c

File tree

4 files changed

+122
-15
lines changed

4 files changed

+122
-15
lines changed

README.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Folders for Claude.AI
2+
3+
This small extension injects the Folder upload button next to file upload to allow for multiple file uploads at once, while respecting Claude's supported file formats.
4+
5+
![screenshot](doc/screenshot.png)
6+
7+
## Under the limit
8+
9+
If your folder has up to 5 files, each file would be uploaded as is with no modifications.
10+
11+
## Combined folder contents
12+
13+
Since Claude has a 5 file upload limit, if your folder contains more files, the folder will be combined into a single file, preserving the directory structure and file names.
14+
15+
For example if your folder looks like this:
16+
17+
```
18+
.
19+
└── ClaudeFoldersTest
20+
├── file1.txt
21+
├── file2.txt
22+
├── subfolder
23+
│   └── file3.txt
24+
├── subfolder2
25+
│   ├── file4.txt
26+
│   └── file5.txt
27+
└── subfolder3
28+
└── file6.txt
29+
30+
5 directories, 6 files
31+
32+
```
33+
34+
The extension will then upload a file named `ClaudeFoldersTest-combined.txt` with contents like this:
35+
36+
```
37+
[File]: file1.txt
38+
contents of file 1
39+
40+
[File]: file2.txt
41+
contents of file 2
42+
43+
[File]: subfolder/file3.txt
44+
contents of file 3 in subfolder
45+
46+
[File]: subfolder2/file4.txt
47+
contents of file 4
48+
49+
[File]: subfolder2/file5.txt
50+
contents of file 5
51+
52+
[File]: subfolder3/file6.txt
53+
contents of file 6
54+
```
55+
56+
This is very useful for programming needs as it allows you to combine all your code files into a single file to feed Claude.
57+
58+
## Ignored files
59+
60+
To include some safety considerations all files that are starting with `.filename` are ignored (.env files for example) and not sent to Claude.
61+
62+
Additionally, in the combined mode where there is more than 5 files, images are uploaded only as <image_content> excluding the file's actual content to keep the context length reasonable.
63+
64+
# Installation
65+
66+
To install the extension to Chrome:
67+
* Checkout or download the git repository from the releases.
68+
* Navigate to `chrome://extensions/` URL.
69+
* Click `Load unpacked` and point to the downloaded location.

content.js

+53-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// content.js
2+
13
function getAcceptedFileTypes() {
24
const fileInput = document.querySelector('input[data-testid="file-upload"]');
35
if (!fileInput) {
@@ -46,7 +48,7 @@ function injectFolderUploadButton() {
4648

4749
const folderUploadButton = document.createElement('button');
4850
folderUploadButton.id = 'claude-folder-upload';
49-
folderUploadButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-up"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M12 10v6"/><path d="m9 13 3-3 3 3"/></svg>';
51+
folderUploadButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-up"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M12 10v6"/><path d="m9 13 3-3 3 3"/></svg>';
5052
folderUploadButton.title = 'Upload Folder';
5153
folderUploadButton.setAttribute('aria-label', 'Upload folder');
5254

@@ -96,25 +98,61 @@ function injectFolderUploadButton() {
9698
}
9799

98100
try {
99-
const dataTransfer = new DataTransfer();
100-
101-
allowedFiles.forEach(file => {
102-
try {
103-
dataTransfer.items.add(file);
104-
} catch (e) {
105-
console.warn('Failed to add file:', file.name, e);
101+
let combinedContent = '';
102+
const rootFolderName = allowedFiles[0].webkitRelativePath.split('/')[0];
103+
104+
if (allowedFiles.length > 5) {
105+
// Sort files to maintain folder structure
106+
allowedFiles.sort((a, b) => a.webkitRelativePath.localeCompare(b.webkitRelativePath));
107+
108+
let currentPath = '';
109+
for (const file of allowedFiles) {
110+
const relativePath = file.webkitRelativePath.slice(rootFolderName.length + 1);
111+
const folderPath = relativePath.split('/').slice(0, -1).join('/');
112+
113+
// Only add folder path if it has changed
114+
if (folderPath !== currentPath) {
115+
currentPath = folderPath;
116+
}
117+
118+
// Format the file entry
119+
combinedContent += `[File]: ${currentPath ? `${currentPath}/` : ''}${file.name}\n`;
120+
121+
if (file.type.startsWith('image/')) {
122+
combinedContent += '<image_content>\n';
123+
} else {
124+
const content = await file.text();
125+
combinedContent += `${content}\n`;
126+
}
106127
}
107-
});
108128

109-
originalInput.files = dataTransfer.files;
110-
originalInput.dispatchEvent(new Event('change', { bubbles: true }));
129+
const combinedFile = new File([combinedContent.trim()], `${rootFolderName}_combined.txt`, { type: 'text/plain' });
130+
131+
const dataTransfer = new DataTransfer();
132+
dataTransfer.items.add(combinedFile);
133+
originalInput.files = dataTransfer.files;
134+
originalInput.dispatchEvent(new Event('change', { bubbles: true }));
135+
136+
showToast(`Successfully combined ${allowedFiles.length} files into one .txt file`);
137+
} else {
138+
const dataTransfer = new DataTransfer();
139+
allowedFiles.forEach(file => {
140+
try {
141+
dataTransfer.items.add(file);
142+
} catch (e) {
143+
console.warn('Failed to add file:', file.name, e);
144+
}
145+
});
146+
147+
originalInput.files = dataTransfer.files;
148+
originalInput.dispatchEvent(new Event('change', { bubbles: true }));
149+
150+
showToast(`Successfully added ${allowedFiles.length} file${allowedFiles.length === 1 ? '' : 's'}`);
151+
}
111152

112-
const fileCount = allowedFiles.length;
113-
let message = `Successfully added ${fileCount} file${fileCount === 1 ? '' : 's'} from folder`;
114153
if (ignoredCount > 0) {
115-
message += `. ${ignoredCount} file${ignoredCount === 1 ? '' : 's'} starting with . were ignored for safety.`;
154+
showToast(`${ignoredCount} file${ignoredCount === 1 ? '' : 's'} starting with . were ignored for safety.`, 5000);
116155
}
117-
showToast(message);
118156
} catch (error) {
119157
console.error('Error processing files:', error);
120158
showToast('An error occurred while processing the files. Please try again with fewer files.');

doc/combined.png

45.4 KB
Loading

doc/screenshot.png

42 KB
Loading

0 commit comments

Comments
 (0)