Skip to content

feat: Add DirectoryInput component with file system directory picker #8389

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

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

Conversation

deon-sanchez
Copy link
Collaborator

@deon-sanchez deon-sanchez commented Jun 5, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a directory input field with a directory picker for easy selection of folder paths.
    • Added support for directory input type across the interface, including a dedicated input component and backend integration.
  • Bug Fixes

    • Updated default directory input value to an empty string for improved usability.
  • Style

    • Enhanced styling for the directory input component with dynamic button and icon states.

@deon-sanchez deon-sanchez self-assigned this Jun 5, 2025
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 5, 2025
Copy link
Contributor

coderabbitai bot commented Jun 5, 2025

Walkthrough

A new directory input type is introduced across the backend and frontend. Backend changes add the DirectoryInput class, update input registries, and modify the directory component to use this new input. Frontend changes add a DirectoryComponent React component, integrate it into parameter rendering, and implement a helper for directory selection using the File System Access API or a fallback.

Changes

File(s) Change Summary
.../components/data/directory.py Switched "path" input from MessageTextInput to DirectoryInput and changed its default to "".
.../inputs/inputs.py Added DirectoryInput class and included it in the InputTypes union.
.../inputs/input_mixin.py Added DIRECTORY = "directory" to the FieldTypes enum.
.../inputs/init.py, .../io/init.py Added DirectoryInput to imports and __all__ for public/exported entities.
.../utils/constants.py Appended "directory" to the DIRECT_TYPES list.
.../parameterRenderComponent/components/directoryComponent/index.tsx Added new DirectoryComponent React component for directory input and selection.
.../parameterRenderComponent/index.tsx Integrated "directory" input type, rendering DirectoryComponent when appropriate.
.../helpers/create-directory-upload.ts Added createDirectoryUpload helper for directory selection using File System Access API or fallback.

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
Loading
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
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4e31d9 and 89a0006.

📒 Files selected for processing (1)
  • src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/src/components/core/parameterRenderComponent/components/directoryComponent/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Update Starter Projects
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Run Ruff Check and Format
  • GitHub Check: Ruff Style Check (3.13)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 createDirectoryUpload helper 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

📥 Commits

Reviewing files that changed from the base of the PR and between a161753 and d89f49e.

📒 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_TYPES is properly placed and aligns with the new DirectoryInput class 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 the FieldTypes enum 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 DirectoryInput to 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 DirectoryComponent is 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 DirectoryComponent with standard baseInputProps and 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 webkitRelativePath and handles edge cases appropriately. The logging is helpful for debugging.


93-102: LGTM! Proper event setup and timing.

The use of requestAnimationFrame ensures 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:

✅ Verification successful

Verify webkitdirectory compatibility with multiple=false.

When using webkitdirectory, browsers typically expect multiple=true to allow selecting all files within a directory. Setting multiple=false might prevent proper directory selection in some browsers.

Please verify that directory selection works correctly with multiple=false across different browsers, or consider changing to:

-    input.multiple = false; // Only single directory selection
+    input.multiple = true; // Required for webkitdirectory

Run this script to check browser compatibility documentation:


🌐 Web query:

Does webkitdirectory attribute work with multiple=false in modern browsers?

💡 Result:

Does webkitdirectory Attribute Work with multiple=false in Modern Browsers?

Summary:
The webkitdirectory attribute 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 the multiple attribute is set to true or false—but its behavior is subtly different depending on the presence of multiple.


Detailed Behavior and Compatibility:

  • What webkitdirectory Does:
    Adding the webkitdirectory attribute 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 the webkitRelativePath property[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 if webkitdirectory is 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 the webkitdirectory attribute, both with and without multiple. On mobile browsers, support is absent or inconsistent[6].

  • Practical Implications:

    • The presence of multiple is 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's files list, regardless of the multiple attribute.
    • You cannot select more than one directory in a single operation, even if multiple is set[1][9].
    • You cannot use webkitdirectory to 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 webkitdirectory attribute works with multiple=false (or with multiple omitted) in modern desktop browsers.

  • Users can pick a single directory per interaction, and all contained files are included in the selection.
  • multiple does not alter this behavior for directory selection.
  • The attribute is widely supported on desktop browsers but not on mobile[1][3][6][9].

Citations:


No change needed for multiple=false with webkitdirectory.

Modern desktop browsers (Chrome, Firefox, Edge, Safari, Opera) fully support directory selection via webkitdirectory regardless of the multiple attribute. Even with multiple=false (or omitted), users can pick one directory per dialog and all contained files are included in the files list. There’s no need to set multiple=true.

@@ -1,6 +1,6 @@
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
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +3 to +4
export const createDirectoryUpload = (): Promise<string> => {
return new Promise(async (resolve) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

@deon-sanchez
Copy link
Collaborator Author

@coderabbitai

Copy link
Contributor

coderabbitai bot commented Jun 9, 2025

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:

  • @coderabbitai explain this code block (on specific lines)
  • @coderabbitai review the error handling in the directory picker
  • @coderabbitai check for potential security issues with directory selection

Documentation & Testing:

  • @coderabbitai generate docstrings for the new DirectoryInput classes
  • @coderabbitai suggest unit tests for the DirectoryComponent
  • @coderabbitai create integration test scenarios for directory selection

Architecture & Improvements:

  • @coderabbitai analyze the consistency between frontend and backend directory handling
  • @coderabbitai suggest improvements for the File System Access API fallback

General:

  • @coderabbitai summary to regenerate the PR summary
  • @coderabbitai help for more options

What would you like me to help you with regarding this directory input implementation?

@deon-sanchez
Copy link
Collaborator Author

@coderabbitai can you update my title

@coderabbitai coderabbitai bot changed the title LFOSS-1255 feat: Add DirectoryInput component with file system directory picker Jun 9, 2025
Copy link
Contributor

coderabbitai bot commented Jun 9, 2025

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:

#!/bin/bash
# Update the PR title to be more descriptive
gh pr edit 8389 --title "feat: Add DirectoryInput component with file system directory picker"

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:

  • feat: indicates it's a new feature
  • DirectoryInput component highlights the main component being added
  • file system directory picker describes the key functionality users will interact with

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.

🐰
/| /|
( ˶ᵔ ᵕ ᵔ˶ )
o_(")(")
Successfully updated!

@github-actions github-actions bot added the enhancement New feature or request label Jun 9, 2025
@deon-sanchez deon-sanchez added the DO NOT MERGE Don't Merge this PR label Jul 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DO NOT MERGE Don't Merge this PR enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant