feat: Add DirectoryInput component with file system directory picker#8389
feat: Add DirectoryInput component with file system directory picker#8389deon-sanchez wants to merge 4 commits intomainfrom
Conversation
WalkthroughA new directory input type is introduced across the backend and frontend. Backend changes add the Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant DirectoryComponent (Frontend)
participant createDirectoryUpload (Helper)
participant Backend API
User->>DirectoryComponent (Frontend): Clicks directory picker button
DirectoryComponent (Frontend)->>createDirectoryUpload (Helper): Invoke directory picker
createDirectoryUpload (Helper)-->>User: Show directory picker dialog
User-->>createDirectoryUpload (Helper): Selects directory or cancels
createDirectoryUpload (Helper)-->>DirectoryComponent (Frontend): Return selected directory path or ""
DirectoryComponent (Frontend)-->>Backend API: (On submit) Send directory path as input
Backend API-->>DirectoryComponent (Frontend): (Optional) Responds with results
sequenceDiagram
participant DirectoryComponent (Backend)
participant DirectoryInput (Backend Input Class)
participant FieldTypes Enum
participant Constants
DirectoryComponent (Backend)->>DirectoryInput (Backend Input Class): Uses as input for "path"
DirectoryInput (Backend Input Class)->>FieldTypes Enum: Sets field_type to DIRECTORY
DirectoryInput (Backend Input Class)->>Constants: "directory" included in DIRECT_TYPES
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms (4)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx (1)
15-27: Remove console.log and consider user experience implications.The async directory picker handler is well-implemented with proper error handling and disabled state checking. However, remove the console.log statement for production code.
- console.log("selectedDirectory", selectedDirectory);Also consider that the
createDirectoryUploadhelper only returns directory names or relative paths (not absolute paths), which may not align with user expectations for a directory path input.src/frontend/src/helpers/create-directory-upload.ts (2)
6-17: Improve type safety for the File System Access API.The current implementation uses
(window as any)which bypasses TypeScript's type checking.Consider adding proper type definitions or a more specific type guard:
- if ("showDirectoryPicker" in window) { - try { - const dirHandle = await (window as any).showDirectoryPicker(); + if ("showDirectoryPicker" in window && typeof (window as any).showDirectoryPicker === 'function') { + try { + const dirHandle = await (window as any).showDirectoryPicker();
83-91: Consider increasing the focus handler timeout.The 100ms timeout in the focus handler might be too short and could trigger false positives when users take a moment to interact with the directory picker dialog.
Consider increasing the timeout to allow for normal user interaction:
setTimeout(() => { if (!isHandled) { isHandled = true; cleanup(); resolve(""); } - }, 100); + }, 500); // Give users more time to interact with the dialog
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/backend/base/langflow/components/data/directory.py(2 hunks)src/backend/base/langflow/inputs/__init__.py(2 hunks)src/backend/base/langflow/inputs/input_mixin.py(1 hunks)src/backend/base/langflow/inputs/inputs.py(2 hunks)src/backend/base/langflow/io/__init__.py(2 hunks)src/backend/base/langflow/utils/constants.py(1 hunks)src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx(1 hunks)src/frontend/src/components/core/parameterRenderComponent/index.tsx(2 hunks)src/frontend/src/helpers/create-directory-upload.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/backend/base/langflow/io/__init__.py (1)
src/backend/base/langflow/inputs/inputs.py (1)
DirectoryInput(625-635)
src/backend/base/langflow/inputs/__init__.py (1)
src/backend/base/langflow/inputs/inputs.py (1)
DirectoryInput(625-635)
src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx (2)
src/frontend/src/helpers/create-directory-upload.ts (1)
createDirectoryUpload(3-112)src/frontend/src/utils/utils.ts (1)
cn(36-38)
src/frontend/src/helpers/create-directory-upload.ts (1)
src/frontend/src/constants/constants.ts (1)
DEFAULT_FILE_PICKER_TIMEOUT(1075-1075)
🪛 Biome (1.9.4)
src/frontend/src/helpers/create-directory-upload.ts
[error] 4-111: Promise executor functions should not be async.
(lint/suspicious/noAsyncPromiseExecutor)
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Optimize new Python code in this PR
- GitHub Check: Run Ruff Check and Format
- GitHub Check: Update Starter Projects
- GitHub Check: Ruff Style Check (3.13)
- GitHub Check: autofix
🔇 Additional comments (16)
src/backend/base/langflow/utils/constants.py (1)
65-65: LGTM! Correct addition of directory type.The addition of "directory" to
DIRECT_TYPESis properly placed and aligns with the newDirectoryInputclass being introduced across the codebase.src/backend/base/langflow/inputs/input_mixin.py (1)
31-31: LGTM! Proper enum extension.The addition of
DIRECTORY = "directory"follows the established pattern and correctly extends theFieldTypesenum to support the new directory input type.src/backend/base/langflow/io/__init__.py (1)
9-9: LGTM! Correct public API integration.The addition of
DirectoryInputto both the import statement and__all__list properly exposes the new directory input class as part of the public API, following the established pattern for other input types.Also applies to: 41-41
src/frontend/src/components/core/parameterRenderComponent/index.tsx (2)
13-13: LGTM! Proper import addition.The import of
DirectoryComponentis correctly placed and follows the established pattern for other parameter render components.
185-188: LGTM! Consistent switch case implementation.The addition of the "directory" case follows the exact pattern used for other input types, correctly rendering the
DirectoryComponentwith standardbaseInputPropsand an appropriately prefixed id.src/backend/base/langflow/inputs/__init__.py (1)
9-9: LGTM! Proper integration of DirectoryInput into the public API.The import and export additions follow the established pattern for input types and correctly make DirectoryInput available for use throughout the codebase.
Also applies to: 44-44
src/backend/base/langflow/inputs/inputs.py (2)
625-635: LGTM! Well-implemented DirectoryInput class.The class follows the established pattern for input types with appropriate inheritance from BaseInputMixin, MetadataTraceMixin, and ToolModeMixin. The docstring is clear and the field_type is correctly set to FieldTypes.DIRECTORY.
672-672: LGTM! Proper type union integration.Adding DirectoryInput to the InputTypes union is necessary for proper type checking and ensures the new input type is recognized throughout the system.
src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx (2)
35-51: Well-designed text input with helpful placeholder.The manual text input provides good fallback functionality and the placeholder text clearly guides users on the expected format. The conditional styling and accessibility attributes are properly implemented.
54-79: Excellent button implementation with clear user guidance.The button component is well-designed with:
- Proper conditional styling based on value and disabled state
- Clear visual feedback with icon changes (Folder → CircleCheckBig)
- Helpful title text that explains the limitation of the directory picker
- Good accessibility with data-testid and proper button attributes
The title text effectively manages user expectations about the picker's limitations.
src/frontend/src/helpers/create-directory-upload.ts (6)
1-2: LGTM!The import statement is clean and follows proper ES6 import syntax.
32-42: LGTM! Good cleanup implementation.The cleanup function properly handles element removal and event listener cleanup with appropriate error handling.
44-81: LGTM! Solid path extraction logic.The change handler correctly extracts the directory path from
webkitRelativePathand handles edge cases appropriately. The logging is helpful for debugging.
93-102: LGTM! Proper event setup and timing.The use of
requestAnimationFrameensures the input is properly rendered before triggering the click event.
104-110: LGTM! Appropriate timeout handling.The timeout implementation using the imported constant provides a reasonable fallback for cases where the user doesn't interact with the picker.
27-28:Details
✅ Verification successful
Verify webkitdirectory compatibility with multiple=false.
When using
webkitdirectory, browsers typically expectmultiple=trueto allow selecting all files within a directory. Settingmultiple=falsemight prevent proper directory selection in some browsers.Please verify that directory selection works correctly with
multiple=falseacross different browsers, or consider changing to:- input.multiple = false; // Only single directory selection + input.multiple = true; // Required for webkitdirectoryRun this script to check browser compatibility documentation:
🌐 Web query:
Does webkitdirectory attribute work with multiple=false in modern browsers?💡 Result:
Does
webkitdirectoryAttribute Work withmultiple=falsein Modern Browsers?Summary:
Thewebkitdirectoryattribute allows users to select entire directories through a file input. In modern desktop browsers (Chrome, Firefox, Edge, Safari, Opera), the attribute works regardless of whether themultipleattribute is set totrueorfalse—but its behavior is subtly different depending on the presence ofmultiple.
Detailed Behavior and Compatibility:
What
webkitdirectoryDoes:
Adding thewebkitdirectoryattribute to an<input type="file">element enables directory selection instead of limiting users to single file selection. When a directory is chosen, all files within that directory and its subdirectories are included in the file list, with their paths preserved via thewebkitRelativePathproperty[1][3][7].Interaction with
multiple:
- If you use
<input type="file" webkitdirectory multiple>, the user experience generally mirrors that of<input type="file" webkitdirectory>, because directory selection inherently means selecting multiple files (all files inside the chosen directory)[1][3][9].- If you omit
multiple—that is,<input type="file" webkitdirectory>—the user can still only pick one directory per dialog interaction, but all the files inside that directory (and subdirectories) are included.- Setting
multiple=false(or leaving it unset, which is the default) doesn’t prevent multiple files from being selected ifwebkitdirectoryis present, because selecting a directory by definition includes all its files[1][3][9].Browser Compatibility:
Modern versions of Chrome, Firefox, Edge, Safari, and Opera on desktop fully support thewebkitdirectoryattribute, both with and withoutmultiple. On mobile browsers, support is absent or inconsistent[6].Practical Implications:
- The presence of
multipleis ignored for the directory picker use case: users can only select one directory per dialog, and all files within it become part of the input'sfileslist, regardless of themultipleattribute.- You cannot select more than one directory in a single operation, even if
multipleis set[1][9].- You cannot use
webkitdirectoryto select a mix of individual files and directories in the same dialog[8].Reference Example (MDN):
The webkitdirectory Boolean attribute, if present, indicates that only directories should be available to be selected by the user in the file picker interface.
...
When a directory is selected, the directory and its entire hierarchy of contents are included in the set of selected items.
...
<input type="file" id="file-picker" name="fileList" webkitdirectory multiple />After the user makes a selection, each File object in files has its File.webkitRelativePath property set to the relative path within the selected directory at which the file is located[1][3][7].
Conclusion
Yes, the
webkitdirectoryattribute works withmultiple=false(or withmultipleomitted) in modern desktop browsers.
- Users can pick a single directory per interaction, and all contained files are included in the selection.
multipledoes not alter this behavior for directory selection.- The attribute is widely supported on desktop browsers but not on mobile[1][3][6][9].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
- 2: sveltejs/svelte#7872
- 3: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/file
- 4: WICG/directory-upload#29
- 5: https://www.smashingmagazine.com/2017/09/uploading-directories-with-webkitdirectory/
- 6: https://www.lambdatest.com/web-technologies/input-file-directory
- 7: https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
- 8: https://issues.chromium.org/41246097
- 9: https://wicg.github.io/entries-api/
- 10: https://issues.chromium.org/issues/41210938
No change needed for
multiple=falsewithwebkitdirectory.Modern desktop browsers (Chrome, Firefox, Edge, Safari, Opera) fully support directory selection via
webkitdirectoryregardless of themultipleattribute. Even withmultiple=false(or omitted), users can pick one directory per dialog and all contained files are included in thefileslist. There’s no need to setmultiple=true.
| from langflow.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths | ||
| from langflow.custom import Component | ||
| from langflow.io import BoolInput, IntInput, MessageTextInput, MultiselectInput | ||
| from langflow.io import BoolInput, DirectoryInput, IntInput, MultiselectInput |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Good transition to DirectoryInput, but reconsider the default value.
The import change and switch to DirectoryInput is appropriate for a directory-specific component. However, changing the default value from "." to "" removes a useful default that represents the current directory.
Consider reverting the default value:
- value="",
+ value=".",The current directory "." is a conventional and practical default that gives users a starting point, while an empty string provides no guidance.
Also applies to: 16-22
🤖 Prompt for AI Agents
In src/backend/base/langflow/components/data/directory.py around lines 3 and 16
to 22, the default value for DirectoryInput was changed from "." to an empty
string "". Revert this change by setting the default value back to ".", which is
a conventional and practical default representing the current directory and
provides a useful starting point for users.
| export const createDirectoryUpload = (): Promise<string> => { | ||
| return new Promise(async (resolve) => { |
There was a problem hiding this comment.
Fix the async Promise executor anti-pattern.
Using async in a Promise executor is an anti-pattern that can lead to unhandled promise rejections. If the async function throws an error, it won't be properly caught by the Promise.
Apply this diff to fix the issue:
-export const createDirectoryUpload = (): Promise<string> => {
- return new Promise(async (resolve) => {
+export const createDirectoryUpload = async (): Promise<string> => {
+ return new Promise((resolve) => {Then wrap the modern API call in an IIFE:
// Try modern File System Access API first (if available)
if ("showDirectoryPicker" in window) {
- try {
- const dirHandle = await (window as any).showDirectoryPicker();
- resolve(dirHandle.name);
- return;
- } catch (error) {
+ (async () => {
+ try {
+ const dirHandle = await (window as any).showDirectoryPicker();
+ resolve(dirHandle.name);
+ return;
+ } catch (error) {
+ // User cancelled or API not supported, fall back to input method
+ console.log(
+ "Directory picker cancelled or not supported, falling back to input method",
+ );
+ // Continue to fallback method
+ }
+ })();
+ return;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const createDirectoryUpload = (): Promise<string> => { | |
| return new Promise(async (resolve) => { | |
| export const createDirectoryUpload = async (): Promise<string> => { | |
| return new Promise((resolve) => { | |
| // Try modern File System Access API first (if available) | |
| if ("showDirectoryPicker" in window) { | |
| (async () => { | |
| try { | |
| const dirHandle = await (window as any).showDirectoryPicker(); | |
| resolve(dirHandle.name); | |
| return; | |
| } catch (error) { | |
| // User cancelled or API not supported, fall back to input method | |
| console.log( | |
| "Directory picker cancelled or not supported, falling back to input method", | |
| ); | |
| // Continue to fallback method | |
| } | |
| })(); | |
| return; | |
| } | |
| // Fallback: prompt file input for directory selection | |
| const input = document.createElement("input"); | |
| input.type = "file"; | |
| (input as any).webkitdirectory = true; | |
| input.onchange = () => { | |
| const files = Array.from(input.files || []); | |
| if (files.length > 0) { | |
| resolve(files[0].webkitRelativePath.split("/")[0]); | |
| } else { | |
| resolve(""); | |
| } | |
| }; | |
| input.click(); | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In src/frontend/src/helpers/create-directory-upload.ts at lines 3 to 4, the
Promise executor function is marked async, which is an anti-pattern and can
cause unhandled promise rejections. To fix this, remove the async keyword from
the Promise executor and instead wrap the asynchronous code inside an
immediately invoked async function expression (IIFE) within the executor. This
ensures proper error handling and avoids the anti-pattern.
|
Tip For best results, initiate chat on the files or code changes. @deon-sanchez Hello! I'm here to help with your PR. Since you mentioned me without a specific request, here are some ways I can assist you with this directory input feature: Code Review & Analysis:
Documentation & Testing:
Architecture & Improvements:
General:
What would you like me to help you with regarding this directory input implementation? |
|
@coderabbitai can you update my title |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@deon-sanchez I'll update your PR title to be more descriptive of the changes you've implemented. 🏁 Script executed: Length of output: 146 @deon-sanchez Perfect! I've successfully updated your PR title from "LFOSS-1255" to "feat: Add DirectoryInput component with file system directory picker". This new title better describes what your PR accomplishes:
The title now clearly communicates that this PR introduces a new way for users to select directories through a proper file system picker interface, rather than manually typing paths. 🐰 |
Summary by CodeRabbit
New Features
Bug Fixes
Style