Fix and Enhance Modal, Button, and Layout Functionality (#2064)

#### Description

This pull request introduces several fixes and enhancements related to
modals, buttons, and layout elements within the application. The changes
improve the user experience by addressing bugs, enhancing functionality,
and refining UI components.

#### Summary of Changes

- **Modals:**
  - Added Submit buttons to every modal, except confirmation modals.
  - Fixed submitting issues on `storeApiKeyModal`.
  - Added a close button when the submit button is present.
  - Fixed padding on `deleteConfirmationModal`.

- **Node and Component Enhancements:**
  - Removed the pencil icon from the node name.
- Made node description editable by clicking once and changed the cursor
type.
  - Changed empty component behavior to open the New Project modal.
  - Fixed the node toolbar to allow moving the nodes.
  - Fixed Endpoint Name labeling.
  - Passed duplicate flow function to the main page.
  - Made folders visually more pleasing.
  - Fixed the tooltip that no longer needs removal of the portal.

- **UI and Layout Adjustments:**
  - Fixed bottom padding on settings pages.
  - Fixed scrolling not working in the global variables dropdown.
  - Disabled accordion when it is empty.
  - Removed shadow from card elements.
  - Added a description column to the advanced tab.
  - Implemented unselect on escape.
- Fixed classes and layout for sidebar buttons, ensuring they don't look
strange and behave consistently.
- Fixed button classes to allow loading indicators and ensure proper
sizing and functionality.
  - Modularized loading on buttons.

- **General Fixes and Improvements:**
  - Changed message of the terminal to "Run Langflow".
- Fixed save functionality to use user-provided API keys instead of
default ones.
- Ensured button components can handle multiple children and look
correct.
  - Added icons to various UI elements.
  - Fixed ID scrolling issue when clicking from Store.
  - Removed extra space caused by an unnecessary div element.
- Returned the loader to default settings and ensured buttons work with
`asChild`.

#### Additional Changes
- Merged the remote-tracking branch `origin/dev` into `fix/minor_bugs`
on multiple occasions to keep the branch up-to-date with the latest
developments.

#### Notes

- Please review the changes related to button classes carefully, as they
impact multiple components.
- Further UI enhancements are planned for the next iteration.
This commit is contained in:
Lucas Oliveira 2024-06-05 19:14:32 +02:00 committed by GitHub
commit 6b41460bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
154 changed files with 14303 additions and 14226 deletions

View file

@ -3,7 +3,8 @@ import Admonition from "@theme/Admonition";
# Custom Components
<Admonition type="info" label="Tip">
Read the [Custom Component Guidelines](../administration/custom-component) for detailed information on custom components.
Read the [Custom Component Guidelines](../administration/custom-component) for
detailed information on custom components.
</Admonition>
Custom components let you extend Langflow by creating reusable and configurable components from a Python script.
@ -31,57 +32,60 @@ This class is the foundation for creating custom components. It allows users to
The following types are supported in the build method:
| Supported Types |
| --------------------------------------------------------- |
| _`str`_, _`int`_, _`float`_, _`bool`_, _`list`_, _`dict`_ |
| _`langflow.field_typing.NestedDict`_ |
| _`langflow.field_typing.Prompt`_ |
| _`langchain.chains.base.Chain`_ |
| _`langchain.PromptTemplate`_ |
| Supported Types |
| ----------------------------------------------------------------- |
| _`str`_, _`int`_, _`float`_, _`bool`_, _`list`_, _`dict`_ |
| _`langflow.field_typing.NestedDict`_ |
| _`langflow.field_typing.Prompt`_ |
| _`langchain.chains.base.Chain`_ |
| _`langchain.PromptTemplate`_ |
| _`from langchain.schema.language_model import BaseLanguageModel`_ |
| _`langchain.Tool`_ |
| _`langchain.document_loaders.base.BaseLoader`_ |
| _`langchain.schema.Document`_ |
| _`langchain.text_splitters.TextSplitter`_ |
| _`langchain.vectorstores.base.VectorStore`_ |
| _`langchain.embeddings.base.Embeddings`_ |
| _`langchain.schema.BaseRetriever`_ |
| _`langchain.Tool`_ |
| _`langchain.document_loaders.base.BaseLoader`_ |
| _`langchain.schema.Document`_ |
| _`langchain.text_splitters.TextSplitter`_ |
| _`langchain.vectorstores.base.VectorStore`_ |
| _`langchain.embeddings.base.Embeddings`_ |
| _`langchain.schema.BaseRetriever`_ |
The difference between _`dict`_ and _`langflow.field_typing.NestedDict`_ is that one adds a simple key-value pair field, while the other opens a more robust dictionary editor.
<Admonition type="info">
Use the `Prompt` type by adding **kwargs to the build method.
If you want to add the values of the variables to the template you defined, format the `PromptTemplate` inside the `CustomComponent` class.
Use the `Prompt` type by adding **kwargs to the build method. If you want to
add the values of the variables to the template you defined, format the
`PromptTemplate` inside the `CustomComponent` class.
</Admonition>
<Admonition type="info">
Use base Python types without a handle by default. To add handles, use the `input_types` key in the `build_config` method.
Use base Python types without a handle by default. To add handles, use the
`input_types` key in the `build_config` method.
</Admonition>
**build_config:** Defines the configuration fields of the component. This method returns a dictionary where each key represents a field name and each value defines the field's behavior.
Supported keys for configuring fields:
| Key | Description |
| --------------------- | --------------------------------------------------- |
| `is_list` | Boolean indicating if the field can hold multiple values. |
| `options` | Dropdown menu options. |
| `multiline` | Boolean indicating if a field allows multiline input. |
| `input_types` | Allows connection handles for string fields. |
| `display_name` | Field name displayed in the UI. |
| `advanced` | Hides the field in the default UI view. |
| `password` | Masks input, useful for sensitive data. |
| `required` | Overrides the default behavior to make a field mandatory. |
| `info` | Tooltip for the field. |
| `file_types` | Accepted file types, useful for file fields. |
| `range_spec` | Defines valid ranges for float fields. |
| `title_case` | Boolean that controls field name capitalization. |
| `refresh_button` | Adds a refresh button that updates field values. |
| `real_time_refresh` | Updates the configuration as field values change. |
| `field_type` | Automatically set based on the build method's type hint. |
| Key | Description |
| ------------------- | --------------------------------------------------------- |
| `is_list` | Boolean indicating if the field can hold multiple values. |
| `options` | Dropdown menu options. |
| `multiline` | Boolean indicating if a field allows multiline input. |
| `input_types` | Allows connection handles for string fields. |
| `display_name` | Field name displayed in the UI. |
| `advanced` | Hides the field in the default UI view. |
| `password` | Masks input, useful for sensitive data. |
| `required` | Overrides the default behavior to make a field mandatory. |
| `info` | Tooltip for the field. |
| `file_types` | Accepted file types, useful for file fields. |
| `range_spec` | Defines valid ranges for float fields. |
| `title_case` | Boolean that controls field name capitalization. |
| `refresh_button` | Adds a refresh button that updates field values. |
| `real_time_refresh` | Updates the configuration as field values change. |
| `field_type` | Automatically set based on the build method's type hint. |
<Admonition type="info" label="Tip">
Use the `update_build_config` method to dynamically update configurations based on field values.
Use the `update_build_config` method to dynamically update configurations
based on field values.
</Admonition>
## Additional methods and attributes
@ -99,4 +103,3 @@ The `CustomComponent` class also provides helpful methods for specific tasks (e.
- `status`: Shows values from the `build` method, useful for debugging.
- `field_order`: Controls the display order of fields.
- `icon`: Sets the canvas display icon.

View file

@ -14,4 +14,4 @@ This component is available under the **Helpers** tab of the Langflow preview.
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/chat_memory.mp4" />
</div>
</div>

View file

@ -18,4 +18,4 @@ This component is available under the **Helpers** tab of the Langflow preview.
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/combine_text.mp4" />
</div>
</div>

View file

@ -14,4 +14,4 @@ The **Create Record** component allows you to dynamically create a `Record` from
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/create_record.mp4" />
</div>
</div>

View file

@ -14,4 +14,4 @@ The **Pass** component enables you to ignore one input and move forward with ano
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/pass.mp4" />
</div>
</div>

View file

@ -14,4 +14,4 @@ The **Message History** component can then be used to retrieve stored messages.
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/store_message.mp4" />
</div>
</div>

View file

@ -12,4 +12,4 @@ The **Sub Flow** component enables a user to select a previously built flow and
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/sub_flow.mp4" />
</div>
</div>

View file

@ -12,4 +12,4 @@ The **Text Operator** component simplifies logic. It evaluates the results from
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/text_operator.mp4" />
</div>
</div>

View file

@ -56,7 +56,8 @@ Components are the building blocks of flows. They consist of inputs, outputs, an
<div style={{ marginBottom: "20px" }}>
During the flow creation process, you will notice handles (colored circles)
attached to one or both sides of a component. These handles represent the
availability to connect to other components. Hover over a handle to see connection details.
availability to connect to other components. Hover over a handle to see
connection details.
</div>
<div style={{ marginBottom: "20px" }}>
@ -85,6 +86,7 @@ Build the flow by clicking the **![Playground icon](/logos/botmessage.svg)Playgr
Once the validation is complete, the status of each validated component should turn green (![Status icon](/logos/greencheck.svg)).
To debug, hover over the component status to see the outputs.
</div>
---
@ -196,6 +198,7 @@ curl -X POST \
```
Result:
```
{"session_id":"f2eefd80-bb91-4190-9279-0d6ffafeaac4:53856a772b8e1cfcb3dd2e71576b5215399e95bae318d3c02101c81b7c252da3","outputs":[{"inputs":{"input_value":"is anybody there?"},"outputs":[{"results":{"result":"Arrr, me hearties! Aye, this be Captain [Your Name] speakin'. What be ye needin', matey?"},"artifacts":{"message":"Arrr, me hearties! Aye, this be Captain [Your Name] speakin'. What be ye needin', matey?","sender":"Machine","sender_name":"AI"},"messages":[{"message":"Arrr, me hearties! Aye, this be Captain [Your Name] speakin'. What be ye needin', matey?","sender":"Machine","sender_name":"AI","component_id":"ChatOutput-njtka"}],"component_display_name":"Chat Output","component_id":"ChatOutput-njtka"}]}]}%
```
@ -231,9 +234,10 @@ A collection is a snapshot of flows available in a database.
Collections can be downloaded to local storage and uploaded for future use.
<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'center' }}>
<ReactPlayer playing controls url='/videos/langflow_collection.mp4'
/>
<div
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
>
<ReactPlayer playing controls url="/videos/langflow_collection.mp4" />
</div>
## Project
@ -276,9 +280,3 @@ To see options for your project, in the upper left corner of the canvas, select
**Export** - Download your current project to your local machine as a `.json` file.
**Undo** or **Redo** - Undo or redo your last action.

View file

@ -1,7 +1,7 @@
import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';
import ZoomableImage from '/src/theme/ZoomableImage.js';
import ReactPlayer from 'react-player';
import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import ReactPlayer from "react-player";
# 🖥️ Flows, components, collections, and projects
@ -17,10 +17,4 @@ A [project](#project) can be a component or a flow. Projects are saved as part o
For example, the **OpenAI LLM** is a **component** of the **Basic prompting** flow, and the **flow** is stored in a **collection**.
## Component

View file

@ -6,33 +6,40 @@ import Admonition from "@theme/Admonition";
# 📦 Install Langflow
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true), to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true),
to create your own Langflow workspace in minutes.
</Admonition>
Langflow requires [Python >=3.10](https://www.python.org/downloads/release/python-3100/) and [pip](https://pypi.org/project/pip/) or [pipx](https://pipx.pypa.io/stable/installation/) to be installed on your system.
Install Langflow with pip:
```bash
python -m pip install langflow -U
```
Install Langflow with pipx:
```bash
pipx install langflow --python python3.10 --fetch-missing-python
```
Pipx can fetch the missing Python version for you with `--fetch-missing-python`, but you can also install the Python version manually.
Pipx can fetch the missing Python version for you with `--fetch-missing-python`, but you can also install the Python version manually.
## Install Langflow pre-release
To install a pre-release version of Langflow:
pip:
```bash
python -m pip install langflow --pre --force-reinstall
```
pipx:
```bash
pipx install langflow --python python3.10 --fetch-missing-python --pip-args="--pre --force-reinstall"
```
@ -52,11 +59,13 @@ python -m langflow --help
## ⛓️ Run Langflow
1. To run Langflow, enter the following command.
```bash
python -m langflow run
```
2. Confirm that a local Langflow instance starts by visiting `http://127.0.0.1:7860` in a Chromium-based browser.
```bash
│ Welcome to ⛓ Langflow │
│ │
@ -83,4 +92,4 @@ You'll be presented with the following screen:
style={{ width: "100%", margin: "20px auto" }}
/>
Name your Space, define the visibility (Public or Private), and click on **Duplicate Space** to start the installation process. When installation is finished, you'll be redirected to the Space's main page to start using Langflow right away!
Name your Space, define the visibility (Public or Private), and click on **Duplicate Space** to start the installation process. When installation is finished, you'll be redirected to the Space's main page to start using Langflow right away!

View file

@ -10,12 +10,15 @@ This guide demonstrates how to build a basic prompt flow and modify that prompt
## Prerequisites
* [Langflow installed and running](./install-langflow.mdx)
- [Langflow installed and running](./install-langflow.mdx)
* [OpenAI API key](https://platform.openai.com)
- [OpenAI API key](https://platform.openai.com)
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Hello World - Basic Prompting
@ -44,25 +47,25 @@ Examine the **Prompt** component. The **Template** field instructs the LLM to `A
This should be interesting...
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
## Run the basic prompting flow
1. Click the **Run** button.
The **Interaction Panel** opens, where you can chat with your bot.
The **Interaction Panel** opens, where you can chat with your bot.
2. Type a message and press Enter.
And... Ahoy! 🏴‍☠️
The bot responds in a piratical manner!
And... Ahoy! 🏴‍☠️
The bot responds in a piratical manner!
## Modify the prompt for a different result
1. To modify your prompt results, in the **Prompt** template, click the **Template** field.
The **Edit Prompt** window opens.
The **Edit Prompt** window opens.
2. Change `Answer the user as if you were a pirate` to a different character, perhaps `Answer the user as if you were Harold Abelson.`
3. Run the basic prompting flow again.
The response will be markedly different.
The response will be markedly different.
## Next steps
@ -72,8 +75,6 @@ By adding Langflow components to your flow, you can create all sorts of interest
Here are a couple of examples:
* [Memory chatbot](/starter-projects/memory-chatbot.mdx)
* [Blog writer](/starter-projects/blog-writer.mdx)
* [Document QA](/starter-projects/document-qa.mdx)
- [Memory chatbot](/starter-projects/memory-chatbot.mdx)
- [Blog writer](/starter-projects/blog-writer.mdx)
- [Document QA](/starter-projects/document-qa.mdx)

View file

@ -29,7 +29,10 @@ Its intuitive interface allows for easy manipulation of AI building blocks, enab
- [Langflow Canvas](/getting-started/canvas) - Learn more about the Langflow canvas.
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Learn more about Langflow 1.0

View file

@ -16,7 +16,7 @@ The `AddContentToPage` component enables you to:
- Convert markdown text to Notion blocks.
- Append the converted blocks to a specified Notion page.
- Seamlessly integrate Notion content creation into Langflow workflows.
</Admonition>
</Admonition>
## Component Usage
@ -105,12 +105,12 @@ class NotionPageCreator(CustomComponent):
Example of using the `AddContentToPage` component in a Langflow flow using Markdown as input:
<ZoomableImage
alt="NotionDatabaseProperties Flow Example"
sources={{
alt="NotionDatabaseProperties Flow Example"
sources={{
light: "img/notion/AddContentToPage_flow_example.png",
dark: "img/notion/AddContentToPage_flow_example.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
In this example, the `AddContentToPage` component connects to a `MarkdownLoader` component to provide the markdown text input. The converted Notion blocks are appended to the specified Notion page using the provided `block_id` and `notion_secret`.
@ -131,8 +131,8 @@ The `AddContentToPage` component is a powerful tool for integrating Notion conte
## Troubleshooting
If you encounter any issues while using the `AddContentToPage` component, consider the following:
- Verify the Notion integration tokens validity and permissions.
- Check the Notion API documentation for updates.
- Ensure markdown text is properly formatted.
- Double-check the `block_id` for correctness.

View file

@ -8,12 +8,12 @@ import ZoomableImage from "/src/theme/ZoomableImage.js";
The Notion integration in Langflow enables seamless connectivity with Notion databases, pages, and users, facilitating automation and improving productivity.
<ZoomableImage
alt="Notion Components in Langflow"
sources={{
alt="Notion Components in Langflow"
sources={{
light: "img/notion/notion_bundle.jpg",
dark: "img/notion/notion_bundle.jpg",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
#### <a target="\_blank" href="json_files/Notion_Components_bundle.json" download>Download Notion Components Bundle</a>

View file

@ -41,7 +41,7 @@ class NotionDatabaseProperties(CustomComponent):
description = "Retrieve properties of a Notion database."
documentation: str = "https://docs.langflow.org/integrations/notion/list-database-properties"
icon = "NotionDirectoryLoader"
def build_config(self):
return {
"database_id": {
@ -80,6 +80,7 @@ class NotionDatabaseProperties(CustomComponent):
```
## Example Usage
<Admonition type="info" title="Example Usage">
Here's an example of how you can use the `NotionDatabaseProperties` component in a Langflow flow:
@ -110,6 +111,7 @@ Feel free to explore the capabilities of the `NotionDatabaseProperties` componen
## Troubleshooting
If you encounter any issues while using the `NotionDatabaseProperties` component, consider the following:
- Verify that the Notion integration token is valid and has the required permissions.
- Check the database ID to ensure it matches the intended Notion database.
- Inspect the response from the Notion API for any error messages or status codes that may indicate the cause of the issue.
- Inspect the response from the Notion API for any error messages or status codes that may indicate the cause of the issue.

View file

@ -140,16 +140,17 @@ class NotionListPages(CustomComponent):
<Admonition type="info" title="Example Usage">
## Example Usage
Here's an example of how you can use the `NotionListPages` component in a Langflow flow and passing to the Prompt component:
<ZoomableImage
alt="NotionListPages
Flow Example"
sources={{
alt="NotionListPages
Flow Example"
sources={{
light: "img/notion/NotionListPages_flow_example.png",
dark: "img/notion/NotionListPages_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
In this example, the `NotionListPages` component is used to retrieve specific pages from a Notion database based on the provided filters and sorting options. The retrieved data can then be processed further in the subsequent components of the flow.
@ -157,7 +158,7 @@ In this example, the `NotionListPages` component is used to retrieve specific pa
## Best Practices
When using the `NotionListPages
When using the `NotionListPages
` component, consider the following best practices:
- Ensure that you have a valid Notion integration token with the necessary permissions to query the desired database.
@ -171,7 +172,7 @@ We encourage you to explore the capabilities of the `NotionListPages
## Troubleshooting
If you encounter any issues while using the `NotionListPages` component, consider the following:
If you encounter any issues while using the `NotionListPages` component, consider the following:
- Double-check that the `notion_secret` and `database_id` are correct and valid.
- Verify that the `query_payload` JSON string is properly formatted and contains valid filtering and sorting options.

View file

@ -15,7 +15,7 @@ The `NotionUserList` component retrieves users from Notion. It provides a conven
- Retrieve user data from Notion
- Access user information such as ID, type, name, and avatar URL
- Integrate Notion user data seamlessly into your Langflow workflows
</Admonition>
</Admonition>
## Component Usage
@ -94,34 +94,34 @@ class NotionUserList(CustomComponent):
```
## Example Usage
<Admonition type="info" title="Example Usage">
Here's an example of how you can use the `NotionUserList` component in a Langflow flow and passing the outputs to the Prompt component:
<ZoomableImage
alt="NotionUserList Flow Example"
sources={{
alt="NotionUserList Flow Example"
sources={{
light: "img/notion/NotionUserList_flow_example.png",
dark: "img/notion/NotionUserList_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
</Admonition>
## Best Practices
When using the `NotionUserList` component, consider the following best practices:
When using the `NotionUserList` component, consider the following best practices:
- Ensure that you have a valid Notion integration token with the necessary permissions to retrieve user data.
- Handle the retrieved user data securely and in compliance with Notion's API usage guidelines.
The `NotionUserList` component provides a seamless way to integrate Notion user data into your Langflow workflows. By leveraging this component, you can easily retrieve and utilize user information from Notion, enhancing the capabilities of your Langflow applications. Feel free to explore and experiment with the `NotionUserList` component to unlock new possibilities in your Langflow projects!
## Troubleshooting
If you encounter any issues while using the `NotionUserList` component, consider the following:
If you encounter any issues while using the `NotionUserList` component, consider the following:
- Double-check that your Notion integration token is valid and has the required permissions.
- Verify that you have installed the necessary dependencies (`requests`) for the component to function properly.
- Check the Notion API documentation for any updates or changes that may affect the component's functionality.
- Check the Notion API documentation for any updates or changes that may affect the component's functionality.

View file

@ -11,7 +11,7 @@ The `NotionPageContent` component retrieves the content of a Notion page as plai
<Admonition type="tip" title="Component Functionality">
The `NotionPageContent` component enables you to:
The `NotionPageContent` component enables you to:
- Retrieve the content of a Notion page as plain text
- Extract text from various block types, including paragraphs, headings, lists, and more
@ -114,18 +114,18 @@ class NotionPageContent(CustomComponent):
Here's an example of how you can use the `NotionPageContent` component in a Langflow flow:
<ZoomableImage
alt="NotionPageContent Flow Example"
sources={{
alt="NotionPageContent Flow Example"
sources={{
light: "img/notion/NotionPageContent_flow_example.png",
dark: "img/notion/NotionPageContent_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
</Admonition>
## Best Practices
When using the `NotionPageContent` component, consider the following best practices:
When using the `NotionPageContent` component, consider the following best practices:
- Ensure that you have the necessary permissions to access the Notion page you want to retrieve.
- Keep your Notion integration token secure and avoid sharing it publicly.
@ -135,7 +135,7 @@ The `NotionPageContent` component provides a seamless way to integrate Notion pa
## Troubleshooting
If you encounter any issues while using the `NotionPageContent` component, consider the following:
If you encounter any issues while using the `NotionPageContent` component, consider the following:
- Double-check that you have provided the correct Notion page ID.
- Verify that your Notion integration token is valid and has the necessary permissions.

View file

@ -97,16 +97,17 @@ class NotionPageCreator(CustomComponent):
```
## Example Usage
<Admonition type="info" title="Example Usage">
Here's an example of how to use the `NotionPageCreator` component in a Langflow flow:
<ZoomableImage
alt="NotionPageCreator Flow Example"
sources={{
alt="NotionPageCreator Flow Example"
sources={{
light: "img/notion/NotionPageCreator_flow_example.png",
dark: "img/notion/NotionPageCreator_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
</Admonition>
@ -124,6 +125,7 @@ The `NotionPageCreator` component simplifies the process of creating pages in a
## Troubleshooting
If you encounter any issues while using the `NotionPageCreator` component, consider the following:
- Double-check that the `database_id` and `notion_secret` inputs are correct and valid.
- Verify that the `properties` input is properly formatted as a JSON string and matches the structure of your Notion database.
- Check the Notion API documentation for any updates or changes that may affect the component's functionality.
- Check the Notion API documentation for any updates or changes that may affect the component's functionality.

View file

@ -109,12 +109,12 @@ Let's break down the key parts of this component:
Here's an example of how to use the `NotionPageUpdate` component in a Langflow flow using:
<ZoomableImage
alt="NotionPageUpdate Flow Example"
sources={{
alt="NotionPageUpdate Flow Example"
sources={{
light: "img/notion/NotionPageUpdate_flow_example.png",
dark: "img/notion/NotionPageUpdate_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
</Admonition>
@ -128,7 +128,6 @@ When using the `NotionPageUpdate` component, consider the following best practic
By leveraging the `NotionPageUpdate` component in Langflow, you can easily integrate updating Notion page properties into your language model workflows and build powerful applications that extend Langflow's capabilities.
## Troubleshooting
If you encounter any issues while using the `NotionPageUpdate` component, consider the following:

View file

@ -146,16 +146,17 @@ class NotionSearch(CustomComponent):
```
## Example Usage
<Admonition type="info" title="Example Usage">
Here's an example of how you can use the `NotionSearch` component in a Langflow flow:
<ZoomableImage
alt="NotionSearch Flow Example"
sources={{
alt="NotionSearch Flow Example"
sources={{
light: "img/notion/NotionSearch_flow_example.png",
dark: "img/notion/NotionSearch_flow_example_dark.png",
}}
style={{ width: "100%", margin: "20px 0" }}
style={{ width: "100%", margin: "20px 0" }}
/>
In this example, the `NotionSearch` component is used to search for pages and databases in Notion based on the provided query and filter criteria. The retrieved data can then be processed further in the subsequent components of the flow.

View file

@ -76,4 +76,3 @@ Refer to the individual component documentation for more details on how to use e
- [Notion Integration Capabilities](https://developers.notion.com/reference/capabilities)
If you encounter any issues or have questions, please reach out to our support team or consult the Langflow community forums.

View file

@ -25,11 +25,11 @@ ModuleNotFoundError: No module named 'langflow.__main__'
There are two possible reasons for this error:
1. You've installed Langflow using _`pip install langflow`_ but you already had a previous version of Langflow installed in your system.
In this case, you might be running the wrong executable.
To solve this issue, run the correct executable by running _`python -m langflow run`_ instead of _`langflow run`_.
If that doesn't work, try uninstalling and reinstalling Langflow with _`python -m pip install langflow --pre -U`_.
In this case, you might be running the wrong executable.
To solve this issue, run the correct executable by running _`python -m langflow run`_ instead of _`langflow run`_.
If that doesn't work, try uninstalling and reinstalling Langflow with _`python -m pip install langflow --pre -U`_.
2. Some version conflicts might have occurred during the installation process.
Run _`python -m pip install langflow --pre -U --force-reinstall`_ to reinstall Langflow and its dependencies.
Run _`python -m pip install langflow --pre -U --force-reinstall`_ to reinstall Langflow and its dependencies.
## _`Something went wrong running migrations. Please, run 'langflow migration --fix'`_
@ -45,4 +45,3 @@ There are two possible reasons for this error:
This error can occur during Langflow upgrades when the new version can't override `langflow-pre.db` in `.cache/langflow/`. Clearing the cache removes this file but will also erase your settings.
If you wish to retain your files, back them up before clearing the folder.

View file

@ -14,12 +14,15 @@ This article demonstrates how to use Langflow's prompt tools to issue basic prom
## Prerequisites
* [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow.mdx)
* [OpenAI API key created](https://platform.openai.com)
- [OpenAI API key created](https://platform.openai.com)
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Create the basic prompting project
@ -42,25 +45,21 @@ Examine the **Prompt** component. The **Template** field instructs the LLM to `A
This should be interesting...
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
## Run the basic prompting flow
1. Click the **Run** button.
The **Interaction Panel** opens, where you can converse with your bot.
The **Interaction Panel** opens, where you can converse with your bot.
2. Type a message and press Enter.
The bot responds in a markedly piratical manner!
The bot responds in a markedly piratical manner!
## Modify the prompt for a different result
1. To modify your prompt results, in the **Prompt** template, click the **Template** field.
The **Edit Prompt** window opens.
The **Edit Prompt** window opens.
2. Change `Answer the user as if you were a pirate` to a different character, perhaps `Answer the user as if you were Harold Abelson.`
3. Run the basic prompting flow again.
The response will be markedly different.
The response will be markedly different.

View file

@ -10,12 +10,15 @@ Build a blog writer with OpenAI that uses URLs for reference content.
## Prerequisites
* [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow.mdx)
* [OpenAI API key created](https://platform.openai.com)
- [OpenAI API key created](https://platform.openai.com)
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Create the Blog Writer project
@ -36,6 +39,7 @@ Build a blog writer with OpenAI that uses URLs for reference content.
This flow creates a one-shot prompt flow with **Prompt**, **OpenAI**, and **Chat Output** components, and augments the flow with reference content and instructions from the **URL** and **Instructions** components.
The **Prompt** component's default **Template** field looks like this:
```bash
Reference 1:
@ -59,16 +63,16 @@ The `{instructions}` value is received from the **Value** field of the **Instruc
The `reference_1` and `reference_2` values are received from the **URL** fields of the **URL** components.
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
## Run the Blog Writer flow
1. Click the **Run** button.
The **Interaction Panel** opens, where you can run your one-shot flow.
The **Interaction Panel** opens, where you can run your one-shot flow.
2. Click the **Lighting Bolt** icon to run your flow.
3. The **OpenAI** component constructs a blog post with the **URL** items as context.
The default **URL** values are for web pages at `promptingguide.ai`, so your blog post will be about prompting LLMs.
The default **URL** values are for web pages at `promptingguide.ai`, so your blog post will be about prompting LLMs.
To write about something different, change the values in the **URL** components, and see what the LLM constructs.
To write about something different, change the values in the **URL** components, and see what the LLM constructs.

View file

@ -10,12 +10,15 @@ Build a question-and-answer chatbot with a document loaded from local memory.
## Prerequisites
* [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow.mdx)
* [OpenAI API key created](https://platform.openai.com)
- [OpenAI API key created](https://platform.openai.com)
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Create the Document QA project
@ -39,24 +42,27 @@ The **Prompt** component is instructed to answer questions based on the contents
Including a file with the prompt gives the **OpenAI** component context it may not otherwise have access to.
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
5. To select a document to load, in the **Files** component, click within the **Path** field.
1. Select a local file, and then click **Open**.
2. The file name appears in the field.
<Admonition type="tip">
The file must be of an extension type listed [here](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/base/data/utils.py#L13).
</Admonition>
1. Select a local file, and then click **Open**.
2. The file name appears in the field.
<Admonition type="tip">
The file must be of an extension type listed
[here](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/base/data/utils.py#L13).
</Admonition>
## Run the Document QA flow
1. Click the **Run** button.
The **Interaction Panel** opens, where you can converse with your bot.
The **Interaction Panel** opens, where you can converse with your bot.
2. Type a message and press Enter.
For this example, we loaded an error log `.txt` file and asked, "What went wrong?"
The bot responded:
For this example, we loaded an error log `.txt` file and asked, "What went wrong?"
The bot responded:
```
The issue occurred during the execution of migrations in the application. Specifically, an error was raised by the Alembic library, indicating that new upgrade operations were detected that had not been accounted for in the existing migration scripts. The operation in question involved modifying the nullable property of a column (apikey, created_at) in the database, with details about the existing type (DATETIME()), existing server default, and other properties.
```

View file

@ -10,12 +10,15 @@ This flow extends the [basic prompting flow](./basic-prompting.mdx) to include c
## Prerequisites
* [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow.mdx)
* [OpenAI API key created](https://platform.openai.com)
- [OpenAI API key created](https://platform.openai.com)
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
## Create the memory chatbot project
@ -43,16 +46,16 @@ This chatbot is augmented with the **Chat Memory** component, which stores messa
The **Chat History** component gives the **OpenAI** component a memory of previous questions.
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
## Run the memory chatbot flow
1. Click the **Run** button.
The **Interaction Panel** opens, where you can converse with your bot.
The **Interaction Panel** opens, where you can converse with your bot.
2. Type a message and press Enter.
The bot will respond according to the template in the **Prompt** component.
The bot will respond according to the template in the **Prompt** component.
3. Type more questions. In the **Outputs** log, your queries are logged in order. Up to 5 queries are stored by default. Try asking `What is the first subject I asked you about?` to see where the LLM's memory disappears.
## Modify the Session ID field to have multiple conversations
@ -65,11 +68,11 @@ You can demonstrate this by modifying the **Session ID** value to switch between
1. In the **Session ID** field of the **Chat Memory** and **Chat Input** components, change the **Session ID** value from `MySessionID` to `AnotherSessionID`.
2. Click the **Run** button to run your flow.
In the **Interaction Panel**, you will have a new conversation. (You may need to clear the cache with the **Eraser** button).
In the **Interaction Panel**, you will have a new conversation. (You may need to clear the cache with the **Eraser** button).
3. Type a few questions to your bot.
4. In the **Session ID** field of the **Chat Memory** and **Chat Input** components, change the **Session ID** value back to `MySessionID`.
5. Run your flow.
The **Outputs** log of the **Interaction Panel** displays the history from your initial chat with `MySessionID`.
The **Outputs** log of the **Interaction Panel** displays the history from your initial chat with `MySessionID`.
## Store Session ID as a Langflow variable
@ -79,4 +82,3 @@ To store **Session ID** as a Langflow variable, in the **Session ID** field, cli
2. In the **Value** field, enter a value like `1B5EBD79-6E9C-4533-B2C8-7E4FF29E983B`.
3. Click **Save Variable**.
4. Apply this variable to **Chat Input**.

View file

@ -17,16 +17,19 @@ We've chosen [Astra DB](https://astra.datastax.com/signup?utm_source=langflow-pr
## Prerequisites
<Admonition type="info">
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true) to create your own Langflow workspace in minutes.
Langflow v1.0 alpha is also available in HuggingFace Spaces. [Clone the space
using this
link](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)
to create your own Langflow workspace in minutes.
</Admonition>
* [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow.mdx)
* [OpenAI API key](https://platform.openai.com)
- [OpenAI API key](https://platform.openai.com)
* [An Astra DB vector database created](https://docs.datastax.com/en/astra-db-serverless/get-started/quickstart.html) with:
* Application token (`AstraCS:WSnyFUhRxsrg…`)
* API endpoint (`https://ASTRA_DB_ID-ASTRA_DB_REGION.apps.astra.datastax.com`)
- [An Astra DB vector database created](https://docs.datastax.com/en/astra-db-serverless/get-started/quickstart.html) with:
- Application token (`AstraCS:WSnyFUhRxsrg…`)
- API endpoint (`https://ASTRA_DB_ID-ASTRA_DB_REGION.apps.astra.datastax.com`)
## Create the vector store RAG project
@ -49,38 +52,40 @@ The **ingestion** flow (bottom of the screen) populates the vector store with da
It ingests data from a file (**File**), splits it into chunks (**Recursive Character Text Splitter**), indexes it in Astra DB (**Astra DB**), and computes embeddings for the chunks (**OpenAI Embeddings**).
This forms a "brain" for the query flow.
The **query** flow (top of the screen) allows users to chat with the embedded vector store data. It's a little more complex:
The **query** flow (top of the screen) allows users to chat with the embedded vector store data. It's a little more complex:
* **Chat Input** component defines where to put the user input coming from the Playground.
* **OpenAI Embeddings** component generates embeddings from the user input.
* **Astra DB Search** component retrieves the most relevant Records from the Astra DB database.
* **Text Output** component turns the Records into Text by concatenating them and also displays it in the Playground.
* **Prompt** component takes in the user input and the retrieved Records as text and builds a prompt for the OpenAI model.
* **OpenAI** component generates a response to the prompt.
* **Chat Output** component displays the response in the Playground.
- **Chat Input** component defines where to put the user input coming from the Playground.
- **OpenAI Embeddings** component generates embeddings from the user input.
- **Astra DB Search** component retrieves the most relevant Records from the Astra DB database.
- **Text Output** component turns the Records into Text by concatenating them and also displays it in the Playground.
- **Prompt** component takes in the user input and the retrieved Records as text and builds a prompt for the OpenAI model.
- **OpenAI** component generates a response to the prompt.
- **Chat Output** component displays the response in the Playground.
4. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
4. To create environment variables for the **Astra DB** and **Astra DB Search** components:
1. In the **Token** field, click the **Globe** button, and then click **Add New Variable**.
2. In the **Variable Name** field, enter `astra_token`.
3. In the **Value** field, paste your Astra application token (`AstraCS:WSnyFUhRxsrg…`).
4. Click **Save Variable**.
5. Repeat the above steps for the **API Endpoint** field, pasting your Astra API Endpoint instead (`https://ASTRA_DB_ID-ASTRA_DB_REGION.apps.astra.datastax.com`).
6. Add the global variable to both the **Astra DB** and **Astra DB Search** components.
1. In the **Variable Name** field, enter `openai_api_key`.
2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
3. Click **Save Variable**.
5. To create environment variables for the **Astra DB** and **Astra DB Search** components:
1. In the **Token** field, click the **Globe** button, and then click **Add New Variable**.
2. In the **Variable Name** field, enter `astra_token`.
3. In the **Value** field, paste your Astra application token (`AstraCS:WSnyFUhRxsrg…`).
4. Click **Save Variable**.
5. Repeat the above steps for the **API Endpoint** field, pasting your Astra API Endpoint instead (`https://ASTRA_DB_ID-ASTRA_DB_REGION.apps.astra.datastax.com`).
6. Add the global variable to both the **Astra DB** and **Astra DB Search** components.
## Run the vector store RAG flow
1. Click the **Playground** button.
The **Playground** opens, where you can chat with your data.
The **Playground** opens, where you can chat with your data.
2. Type a message and press Enter. (Try something like "What topics do you know about?")
3. The bot will respond with a summary of the data you've embedded.
For example, we embedded a PDF of an engine maintenance manual and asked, "How do I change the oil?"
The bot responds:
```
To change the oil in the engine, follow these steps:
@ -102,7 +107,3 @@ You should use a 3/8 inch wrench to remove the oil drain cap.
```
This is the size the engine manual lists as well. This confirms our flow works, because the query returns the unique knowledge we embedded from the Astra vector store.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -20,8 +20,7 @@ When running as a [spot (preemptible) instance](https://cloud.google.com/compute
## Pricing (approximate)
> For a more accurate breakdown of costs, please use the [**GCP Pricing Calculator**](https://cloud.google.com/products/calculator)
> <br>
> For a more accurate breakdown of costs, please use the [**GCP Pricing Calculator**](https://cloud.google.com/products/calculator) > <br>
| Component | Regular Cost (Hourly) | Regular Cost (Monthly) | Spot/Preemptible Cost (Hourly) | Spot/Preemptible Cost (Monthly) | Notes |
| ------------------ | --------------------- | ---------------------- | ------------------------------ | ------------------------------- | -------------------------------------------------------------------------- |

View file

@ -121,7 +121,7 @@ def run(
),
):
"""
Run the Langflow.
Run Langflow.
"""
configure(log_level=log_level, log_file=log_file)

File diff suppressed because one or more lines are too long

View file

@ -40,6 +40,7 @@
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"esbuild": "^0.17.19",
@ -5670,6 +5671,11 @@
"node": ">=12"
}
},
"node_modules/debounce-promise": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",

View file

@ -35,6 +35,7 @@
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"esbuild": "^0.17.19",

View file

@ -15,7 +15,7 @@ dotenv.config({ path: path.resolve(__dirname, "../../.env") });
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
@ -52,18 +52,18 @@ export default defineConfig({
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
launchOptions: {
firefoxUserPrefs: {
"dom.events.asyncClipboard.readText": true,
"dom.events.testing.asyncClipboard": true,
},
},
},
},
// {
// name: "firefox",
// use: {
// ...devices["Desktop Firefox"],
// launchOptions: {
// firefoxUserPrefs: {
// "dom.events.asyncClipboard.readText": true,
// "dom.events.testing.asyncClipboard": true,
// },
// },
// },
// },
],
webServer: [
{

View file

@ -30,10 +30,10 @@ export default function App() {
useTrackLastVisitedPath();
const removeFromTempNotificationList = useAlertStore(
(state) => state.removeFromTempNotificationList
(state) => state.removeFromTempNotificationList,
);
const tempNotificationList = useAlertStore(
(state) => state.tempNotificationList
(state) => state.tempNotificationList,
);
const [fetchError, setFetchError] = useState(false);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
@ -51,7 +51,7 @@ export default function App() {
const refreshVersion = useDarkStore((state) => state.refreshVersion);
const refreshStars = useDarkStore((state) => state.refreshStars);
const setGlobalVariables = useGlobalVariablesStore(
(state) => state.setGlobalVariables
(state) => state.setGlobalVariables,
);
const checkHasStore = useStoreStore((state) => state.checkHasStore);
const navigate = useNavigate();
@ -222,12 +222,19 @@ export default function App() {
id={alert.id}
removeAlert={removeAlert}
/>
) : alert.type === "notice" ? (
<NoticeAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
) : (
alert.type === "notice" && (
<NoticeAlert
alert.type === "success" && (
<SuccessAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
@ -236,20 +243,6 @@ export default function App() {
</div>
))}
</div>
<div className="z-40 flex flex-col-reverse">
{tempNotificationList.map((alert) => (
<div key={alert.id}>
{alert.type === "success" && (
<SuccessAlert
key={alert.id}
title={alert.title}
id={alert.id}
removeAlert={removeAlert}
/>
)}
</div>
))}
</div>
</div>
</div>
);

View file

@ -16,13 +16,13 @@ export default function AlertDropdown({
}: AlertDropdownType): JSX.Element {
const notificationList = useAlertStore((state) => state.notificationList);
const clearNotificationList = useAlertStore(
(state) => state.clearNotificationList
(state) => state.clearNotificationList,
);
const removeFromNotificationList = useAlertStore(
(state) => state.removeFromNotificationList
(state) => state.removeFromNotificationList,
);
const setNotificationCenter = useAlertStore(
(state) => state.setNotificationCenter
(state) => state.setNotificationCenter,
);
const [open, setOpen] = useState(false);
@ -36,7 +36,7 @@ export default function AlertDropdown({
}}
>
<PopoverTrigger>{children}</PopoverTrigger>
<PopoverContent className="nocopy nopan nodelete nodrag noundo flex h-[500px] w-[500px] flex-col">
<PopoverContent className="nocopy nowheel nopan nodelete nodrag noundo flex h-[500px] w-[500px] flex-col">
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications
<div className="flex gap-3 pr-3 ">

View file

@ -40,7 +40,7 @@ export default function ErrorAlert({
removeAlert(id);
}, 500);
}}
className="error-build-message nocopy nopan nodelete nodrag noundo"
className="error-build-message nocopy nowheel nopan nodelete nodrag noundo"
>
<div className="flex">
<div className="flex-shrink-0">

View file

@ -36,7 +36,7 @@ export default function NoticeAlert({
setShow(false);
removeAlert(id);
}}
className="nocopy nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
className="nocopy nowheel nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
>
<div className="flex">
<div className="flex-shrink-0">

View file

@ -34,7 +34,7 @@ export default function SuccessAlert({
setShow(false);
removeAlert(id);
}}
className="success-alert nocopy nopan nodelete nodrag noundo"
className="success-alert nocopy nowheel nopan nodelete nodrag noundo"
>
<div className="flex">
<div className="flex-shrink-0">

View file

@ -15,7 +15,7 @@ export default function FolderAccordionComponent({
options,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion()
open.length === 0 ? "" : getOpenAccordion(),
);
function getOpenAccordion(): string {

View file

@ -6,10 +6,13 @@ import {
AccordionTrigger,
} from "../../components/ui/accordion";
import { AccordionComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import ShadTooltip from "../shadTooltipComponent";
export default function AccordionComponent({
trigger,
children,
disabled,
open = [],
keyValue,
sideBar,
@ -29,7 +32,9 @@ export default function AccordionComponent({
}
function handleClick(): void {
value === "" ? setValue(keyValue!) : setValue("");
if (!disabled) {
value === "" ? setValue(keyValue!) : setValue("");
}
}
return (
@ -38,16 +43,18 @@ export default function AccordionComponent({
type="single"
className="w-full"
value={value}
onValueChange={setValue}
onValueChange={!disabled ? setValue : () => {}}
>
<AccordionItem value={keyValue!} className="border-b">
<AccordionTrigger
onClick={() => {
handleClick();
}}
className={
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3"
}
disabled={disabled}
className={cn(
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3",
disabled ? "cursor-not-allowed" : "cursor-pointer",
)}
>
{trigger}
</AccordionTrigger>

View file

@ -7,7 +7,6 @@ import { useTypesStore } from "../../stores/typesStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Textarea } from "../ui/textarea";
@ -24,19 +23,19 @@ export default function AddNewVariableButton({ children }): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)),
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields))
);
const availableFields = () => {
const fields = Array.from(componentFields).filter(
(field) => !unavaliableFields.has(field),
(field) => !unavaliableFields.has(field)
);
return sortByName(fields);
};
const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable,
(state) => state.addGlobalVariable
);
function handleSaveVariable() {
@ -65,12 +64,17 @@ export default function AddNewVariableButton({ children }): JSX.Element {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error creating variable",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
});
}
return (
<BaseModal open={open} setOpen={setOpen} size="x-small">
<BaseModal
open={open}
setOpen={setOpen}
size="x-small"
onSubmit={handleSaveVariable}
>
<BaseModal.Header
description={
"This variable will be encrypted and will be available for you to use in any of your projects."
@ -137,9 +141,9 @@ export default function AddNewVariableButton({ children }): JSX.Element {
></InputComponent>
</div>
</BaseModal.Content>
<BaseModal.Footer>
<Button onClick={handleSaveVariable}>Save Variable</Button>
</BaseModal.Footer>
<BaseModal.Footer
submit={{ label: "Save Variable", dataTestId: "save-variable-btn" }}
/>
</BaseModal>
);
}

View file

@ -10,7 +10,7 @@ export default function DragCardComponent({ data }: { data: storeComponent }) {
draggable
//TODO check color schema
className={cn(
"group relative flex flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]"
"group relative flex flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
)}
>
<div>
@ -22,7 +22,7 @@ export default function DragCardComponent({ data }: { data: storeComponent }) {
"visible flex-shrink-0",
data.is_component
? "mx-0.5 h-6 w-6 text-component-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon",
)}
name={data.is_component ? "ToyBrick" : "Group"}
/>

View file

@ -18,7 +18,7 @@ export default function CodeAreaComponent({
setOpen,
}: CodeAreaComponentType) {
const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
typeof value == "string" ? value : JSON.stringify(value),
);
useEffect(() => {
if (disabled && myValue !== "") {

View file

@ -29,7 +29,7 @@ export default function DictComponent({
<div
className={classNames(
value.length > 1 && editNode ? "my-1" : "",
"flex flex-col gap-3"
"flex flex-col gap-3",
)}
>
{

View file

@ -59,7 +59,7 @@ export default function Dropdown({
? "dropdown-component-outline"
: "dropdown-component-false-outline",
"w-full justify-between font-normal",
editNode ? "input-edit-node" : "py-2"
editNode ? "input-edit-node" : "py-2",
)}
>
<span data-testid={`value-dropdown-` + id}>
@ -107,7 +107,7 @@ export default function Dropdown({
name="Check"
className={cn(
"ml-auto h-4 w-4 text-primary",
value === option ? "opacity-100" : "opacity-0"
value === option ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>

View file

@ -99,7 +99,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
<span
className={cn(
"font-normal text-muted-foreground word-break-break-word",
description === "" ? "font-light italic" : ""
description === "" ? "font-light italic" : "",
)}
>
{description === "" ? "No description" : description}
@ -109,7 +109,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
{setEndpointName && (
<Label>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium">Endpoint name:</span>
<span className="font-medium">Endpoint Name</span>
{!isEndpointNameValid && (
<span className="edit-flow-span">
Invalid endpoint name. Use only letters, numbers, hyphens, and
@ -123,7 +123,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
type="text"
name="endpoint_name"
value={endpointName ?? ""}
placeholder="An alternative name for the run endpoint"
placeholder="An alternative name to run the endpoint"
maxLength={maxLength}
id="endpoint_name"
onDoubleClickCapture={(event) => {

View file

@ -1,7 +1,6 @@
import BaseModal from "../../modals/baseModal";
import { fetchErrorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
export default function FetchErrorComponent({
message,
@ -12,7 +11,14 @@ export default function FetchErrorComponent({
}: fetchErrorComponentType) {
return (
<>
<BaseModal size="small-h-full" open={openModal} type="modal">
<BaseModal
size="small-h-full"
open={openModal}
type="modal"
onSubmit={() => {
setRetry();
}}
>
<BaseModal.Content>
<div role="status" className="m-auto flex flex-col items-center">
<IconComponent
@ -27,24 +33,9 @@ export default function FetchErrorComponent({
</div>
</BaseModal.Content>
<BaseModal.Footer>
<div className="m-auto">
<Button
disabled={isLoadingHealth}
onClick={() => {
setRetry();
}}
>
{isLoadingHealth ? (
<div>
<IconComponent name={"Loader2"} className={"animate-spin"} />
</div>
) : (
"Retry"
)}
</Button>
</div>
</BaseModal.Footer>
<BaseModal.Footer
submit={{ label: "Retry", loading: isLoadingHealth }}
/>
</BaseModal>
</>
);

View file

@ -18,7 +18,7 @@ export const ForwardedIconComponent = memo(
strokeWidth,
id = "",
}: IconComponentProps,
ref
ref,
) => {
const [showFallback, setShowFallback] = useState(false);
@ -65,8 +65,8 @@ export const ForwardedIconComponent = memo(
/>
</Suspense>
);
}
)
},
),
);
export default ForwardedIconComponent;

View file

@ -35,21 +35,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
const navigate = useNavigate();
const isBuilding = useFlowStore((state) => state.isBuilding);
function handleAddFlow(duplicate?: boolean) {
function handleAddFlow() {
try {
if (duplicate) {
if (!currentFlow) {
throw new Error("No flow to duplicate");
}
addFlow(true, currentFlow).then((id) => {
setSuccessData({ title: "Flow duplicated successfully" });
navigate("/flow/" + id);
});
} else {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
}
@ -89,15 +79,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
handleAddFlow(true);
}}
className="cursor-pointer"
>
<IconComponent name="Copy" className="header-menu-options" />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
@ -132,7 +113,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
title: UPLOAD_ERROR_ALERT,
list: [error],
});
}
},
);
}}
>
@ -214,7 +195,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn(
"h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
)}
/>
{printByBuildStatus()}

View file

@ -56,7 +56,7 @@ export default function Header(): JSX.Element {
const lastFlowVisitedIndex = routeHistory
.reverse()
.findIndex(
(path) => path.includes("/flow/") && path !== location.pathname
(path) => path.includes("/flow/") && path !== location.pathname,
);
const lastFlowVisited = routeHistory[lastFlowVisitedIndex];

View file

@ -32,11 +32,11 @@ export default function HorizontalScrollFadeComponent({
fadeContainerRef.current.classList.toggle(
"fade-left",
isScrollable && !atStart
isScrollable && !atStart,
);
fadeContainerRef.current.classList.toggle(
"fade-right",
isScrollable && !atEnd
isScrollable && !atEnd,
);
};

View file

@ -108,7 +108,7 @@ const CustomInputPopover = ({
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"

View file

@ -80,7 +80,7 @@ const CustomInputPopoverObject = ({
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"

View file

@ -104,8 +104,8 @@ export default function InputFileComponent({
editNode
? "input-edit-node input-dialog text-muted-foreground"
: disabled
? "input-disable input-dialog primary-input"
: "input-dialog primary-input text-muted-foreground"
? "input-disable input-dialog primary-input"
: "input-dialog primary-input text-muted-foreground"
}
>
{myValue !== "" ? myValue : "No file"}

View file

@ -19,15 +19,15 @@ export default function InputGlobalComponent({
editNode = false,
}: InputGlobalComponentType): JSX.Element {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries
(state) => state.globalVariablesEntries,
);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const unavaliableFields = useGlobalVariablesStore(
(state) => state.unavaliableFields
(state) => state.unavaliableFields,
);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable
(state) => state.removeGlobalVariable,
);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -130,7 +130,7 @@ export default function InputGlobalComponent({
<ForwardedIconComponent
name="Trash2"
className={cn(
"h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100"
"h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100",
)}
aria-hidden="true"
/>

View file

@ -55,10 +55,11 @@ export default function InputListComponent({
/>
{idx === value.length - 1 ? (
<button
onClick={() => {
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.push("");
onChange(newInputList);
e.preventDefault();
}}
data-testid={
`input-list-plus-btn${
@ -79,10 +80,11 @@ export default function InputListComponent({
editNode ? "-edit" : ""
}_${componentName}-` + idx
}
onClick={() => {
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.splice(idx, 1);
onChange(newInputList);
e.preventDefault();
}}
disabled={disabled || playgroundDisabled}
>

View file

@ -13,7 +13,6 @@ export default function ShadTooltip({
return (
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent
className={cn(styleClasses, "max-w-96")}
side={side}

View file

@ -1,6 +1,7 @@
import { Link } from "react-router-dom";
import { cn } from "../../../../utils/utils";
import { buttonVariants } from "../../../ui/button";
import ForwardedIconComponent from "../../../genericIconComponent";
type SideBarButtonsComponentProps = {
items: {
@ -11,9 +12,12 @@ type SideBarButtonsComponentProps = {
pathname: string;
handleOpenNewFolderModal?: () => void;
};
const SideBarButtonsComponent = ({ items }: SideBarButtonsComponentProps) => {
const SideBarButtonsComponent = ({
items,
pathname,
}: SideBarButtonsComponentProps) => {
return (
<>
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
{items.map((item) => (
<Link to={item.href!}>
<div
@ -21,14 +25,20 @@ const SideBarButtonsComponent = ({ items }: SideBarButtonsComponentProps) => {
data-testid={`sidebar-nav-${item.title}`}
className={cn(
buttonVariants({ variant: "ghost" }),
"!w-[200px] cursor-pointer justify-start gap-2 border border-transparent hover:border-border hover:bg-transparent"
pathname === item.href
? "border border-border bg-muted hover:bg-muted"
: "border border-transparent hover:border-border hover:bg-transparent",
"flex w-full shrink-0 justify-start gap-4",
)}
>
{item.title}
{item.icon}
<span className="block max-w-full truncate opacity-100">
{item.title}
</span>
</div>
</Link>
))}
</>
</div>
);
};
export default SideBarButtonsComponent;

View file

@ -93,24 +93,25 @@ const SideBarFoldersButtonsComponent = ({
return (
<>
<div className="flex shrink-0 items-center justify-between">
<Button variant="primary" onClick={addNewFolder}>
<ForwardedIconComponent
name="Plus"
className="main-page-nav-button"
/>
New Folder
<div className="flex shrink-0 items-center justify-between gap-2">
<div className="flex-1 self-start text-lg font-semibold">Folders</div>
<Button
variant="primary"
size="icon"
className="px-2"
onClick={addNewFolder}
data-testid="add-folder-button"
>
<ForwardedIconComponent name="FolderPlus" className="w-4" />
</Button>
<Button
variant="primary"
className="px-7"
size="icon"
className="px-2"
onClick={handleUploadFlowsToFolder}
data-testid="upload-folder-button"
>
<ForwardedIconComponent
name="Upload"
className="main-page-nav-button"
/>
Upload
<ForwardedIconComponent name="Upload" className="w-4" />
</Button>
</div>
@ -176,11 +177,11 @@ const SideBarFoldersButtonsComponent = ({
event.stopPropagation();
event.preventDefault();
}}
className="flex w-full items-center gap-2"
className="flex w-full items-center gap-4"
>
<IconComponent
name={"folder"}
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
className="w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
/>
{editFolderName?.edit ? (
<div>
@ -259,14 +260,14 @@ const SideBarFoldersButtonsComponent = ({
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
/>
</div>
) : (
<span className="block max-w-full truncate opacity-100">
<span className="block w-full truncate opacity-100">
{item.name}
</span>
)}
<div className="flex-1" />
{index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
@ -283,21 +284,6 @@ const SideBarFoldersButtonsComponent = ({
/>
</Button>
)}
{/* {index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
>
<IconComponent
name={"pencil"}
className=" w-4 stroke-[1.5] text-white "
/>
</Button>
)} */}
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
@ -305,7 +291,8 @@ const SideBarFoldersButtonsComponent = ({
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
size="none"
variant="none"
>
<IconComponent
name={"Download"}

View file

@ -41,16 +41,20 @@ export default function SidebarNav({
return (
<nav className={cn(className)} {...props}>
<HorizontalScrollFadeComponent>
<SideBarButtonsComponent items={items} pathname={pathname} />
{!loadingFolders && folders?.length > 0 && isFolderPath && (
<SideBarFoldersButtonsComponent
folders={folders}
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleEditFolder={handleEditFolder}
handleDeleteFolder={handleDeleteFolder}
/>
{items.length > 0 ? (
<SideBarButtonsComponent items={items} pathname={pathname} />
) : (
!loadingFolders &&
folders?.length > 0 &&
isFolderPath && (
<SideBarFoldersButtonsComponent
folders={folders}
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleEditFolder={handleEditFolder}
handleDeleteFolder={handleDeleteFolder}
/>
)
)}
</HorizontalScrollFadeComponent>
</nav>

View file

@ -34,7 +34,7 @@ export default function TableAutoCellRender({
variant="outline"
size="sq"
className={cn(
"min-w-min bg-success-background text-success-foreground hover:bg-success-background"
"min-w-min bg-success-background text-success-foreground hover:bg-success-background",
)}
>
{value}

View file

@ -48,7 +48,7 @@ export function TagsSelector({
className={cn(
selectedTags.some((category) => category === tag.name)
? "min-w-min bg-beta-foreground text-background hover:bg-beta-foreground"
: ""
: "",
)}
>
{tag.name}

View file

@ -4,6 +4,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { cn } from "../../utils/utils";
import ShadTooltip from "../shadTooltipComponent";
const Accordion = AccordionPrimitive.Root;
@ -22,17 +23,33 @@ AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
>(({ className, children, disabled, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
<AccordionPrimitive.Trigger
disabled={disabled}
asChild
ref={ref}
{...props}
>
<div
className={cn(
"flex flex-1 cursor-pointer items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className
className,
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
<ShadTooltip
styleClasses="z-50"
content={disabled ? "Empty" : ""}
side="top"
>
<ChevronDownIcon
className={cn(
"h-4 w-4 font-bold transition-transform duration-200",
disabled ? "text-muted-foreground" : "text-primary",
)}
/>
</ShadTooltip>
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
@ -47,7 +64,7 @@ const AccordionContent = React.forwardRef<
ref={ref}
className={cn(
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm",
className
className,
)}
{...props}
>

View file

@ -15,7 +15,7 @@ const alertVariants = cva(
defaultVariants: {
variant: "default",
},
}
},
);
const Alert = React.forwardRef<

View file

@ -2,9 +2,10 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
@ -19,6 +20,7 @@ const buttonVariants = cva(
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
none: "",
},
size: {
default: "h-10 py-2 px-4",
@ -26,19 +28,21 @@ const buttonVariants = cva(
xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md",
none: "",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
}
function toTitleCase(text: string) {
@ -49,21 +53,49 @@ function toTitleCase(text: string) {
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, children, ...props }, ref) => {
(
{
className,
variant,
size,
loading,
disabled,
asChild = false,
children,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
let newChildren = children;
if (typeof children === "string") {
newChildren = toTitleCase(children);
}
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
children={newChildren}
{...props}
/>
<>
<Comp
className={cn(buttonVariants({ variant, size, className }))}
disabled={loading || disabled}
ref={ref}
{...props}
>
{loading ? (
<span className="relative">
<span className="invisible">{newChildren}</span>
<span className="absolute inset-0 flex items-center justify-center">
<ForwardedIconComponent
name={"Loader2"}
className={"animate-spin"}
/>
</span>
</span>
) : (
newChildren
)}
</Comp>
</>
);
}
},
);
Button.displayName = "Button";

View file

@ -8,8 +8,8 @@ const Card = React.forwardRef<
<div
ref={ref}
className={cn(
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all hover:shadow-lg",
className
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all",
className,
)}
{...props}
/>
@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
ref={ref}
className={cn(
"text-base font-semibold leading-tight tracking-tight",
className
className,
)}
{...props}
/>

View file

@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
className,
)}
{...props}
>
@ -37,7 +37,7 @@ const CheckBoxDiv = ({
className={cn(
className,
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
checked ? "bg-primary text-primary-foreground" : ""
checked ? "bg-primary text-primary-foreground" : "",
)}
>
{checked && (

View file

@ -24,7 +24,7 @@ const AccordionTrigger = React.forwardRef<
<div
className={cn(
" flex flex-1 cursor-pointer items-center justify-between border-[1px] py-2 text-sm font-medium data-[state=closed]:rounded-md data-[state=open]:rounded-t-md data-[state=open]:border-b-0 data-[state=open]:bg-muted [&[data-state=open]>svg]:rotate-180",
className
className,
)}
>
{children}
@ -43,7 +43,7 @@ const AccordionContent = React.forwardRef<
ref={ref}
className={cn(
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden border-[1px] text-sm data-[state=open]:rounded-b-md data-[state=open]:border-t-0 data-[state=open]:bg-muted",
className
className,
)}
{...props}
>

View file

@ -16,18 +16,18 @@ const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
@ -66,7 +66,7 @@ type FormItemContextValue = {
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<

View file

@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
className,
)}
{...props}
/>
@ -28,4 +28,26 @@ const TooltipContent = React.forwardRef<
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
const TooltipContentWithoutPortal = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className,
)}
{...props}
/>
));
TooltipContentWithoutPortal.displayName = TooltipPrimitive.Content.displayName;
export {
Tooltip,
TooltipContent,
TooltipContentWithoutPortal,
TooltipProvider,
TooltipTrigger,
};

View file

@ -590,6 +590,7 @@ export const CONTROL_PATCH_USER_STATE = {
password: "",
cnfPassword: "",
gradient: "",
apikey: "",
};
export const CONTROL_LOGIN_STATE = {

View file

@ -2,11 +2,11 @@ import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { renewAccessToken } from ".";
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { checkDuplicateRequestAndStoreRequest } from "./helpers/check-duplicate-requests";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
@ -81,28 +81,12 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use(
(config) => {
const lastUrl = localStorage.getItem("lastUrlCalled");
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
const checkRequest = checkDuplicateRequestAndStoreRequest(config);
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
config?.url!.includes(request),
);
if (
config?.url === lastUrl &&
!isContained &&
lastMethodCalled === config.method
) {
return Promise.reject("Duplicate request");
if (!checkRequest) {
return Promise.reject("Duplicate request.");
}
localStorage.setItem("lastUrlCalled", config.url ?? "");
localStorage.setItem("lastMethodCalled", config.method ?? "");
localStorage.setItem(
"lastRequestData",
JSON.stringify(config.data) ?? "",
);
const accessToken = cookies.get("access_token_lf");
if (accessToken && !isAuthorizedURL(config?.url)) {
config.headers["Authorization"] = `Bearer ${accessToken}`;

View file

@ -0,0 +1,30 @@
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../../constants/constants";
export function checkDuplicateRequestAndStoreRequest(config) {
const lastUrl = localStorage.getItem("lastUrlCalled");
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
const lastRequestTime = localStorage.getItem("lastRequestTime");
const currentTime = Date.now();
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
config?.url!.includes(request),
);
if (
config?.url === lastUrl &&
!isContained &&
lastMethodCalled === config.method &&
lastMethodCalled === "get" && // Assuming you want to check only for GET requests
lastRequestTime &&
currentTime - parseInt(lastRequestTime, 10) < 800
) {
return false;
}
localStorage.setItem("lastUrlCalled", config.url ?? "");
localStorage.setItem("lastMethodCalled", config.method ?? "");
localStorage.setItem("lastRequestTime", currentTime.toString());
return true;
}

View file

@ -61,7 +61,7 @@ export async function sendAll(data: sendAllProps) {
}
export async function postValidateCode(
code: string
code: string,
): Promise<AxiosResponse<errorsTypeAPI>> {
return await api.post(`${BASE_URL_API}validate/code`, { code });
}
@ -76,7 +76,7 @@ export async function postValidateCode(
export async function postValidatePrompt(
name: string,
template: string,
frontend_node: APIClassType
frontend_node: APIClassType,
): Promise<AxiosResponse<PromptTypeAPI>> {
return api.post(`${BASE_URL_API}validate/prompt`, {
name,
@ -149,7 +149,7 @@ export async function saveFlowToDatabase(newFlow: {
* @throws Will throw an error if the update fails.
*/
export async function updateFlowInDatabase(
updatedFlow: FlowType
updatedFlow: FlowType,
): Promise<FlowType> {
try {
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
@ -327,7 +327,7 @@ export async function getHealth() {
*
*/
export async function getBuildStatus(
flowId: string
flowId: string,
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
}
@ -340,7 +340,7 @@ export async function getBuildStatus(
*
*/
export async function postBuildInit(
flow: FlowType
flow: FlowType,
): Promise<AxiosResponse<InitTypeAPI>> {
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
}
@ -356,7 +356,7 @@ export async function postBuildInit(
*/
export async function uploadFile(
file: File,
id: string
id: string,
): Promise<AxiosResponse<UploadFileTypeAPI>> {
const formData = new FormData();
formData.append("file", file);
@ -365,7 +365,7 @@ export async function uploadFile(
export async function postCustomComponent(
code: string,
apiClass: APIClassType
apiClass: APIClassType,
): Promise<AxiosResponse<APIClassType>> {
// let template = apiClass.template;
return await api.post(`${BASE_URL_API}custom_component`, {
@ -378,7 +378,7 @@ export async function postCustomComponentUpdate(
code: string,
template: APITemplateType,
field: string,
field_value: any
field_value: any,
): Promise<AxiosResponse<APIClassType>> {
return await api.post(`${BASE_URL_API}custom_component/update`, {
code,
@ -400,7 +400,7 @@ export async function onLogin(user: LoginType) {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
},
);
if (response.status === 200) {
@ -462,11 +462,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
export async function getUsersPage(
skip: number,
limit: number
limit: number,
): Promise<Array<Users>> {
try {
const res = await api.get(
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`,
);
if (res.status === 200) {
return res.data;
@ -503,7 +503,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
try {
const res = await api.patch(
`${BASE_URL_API}users/${user_id}/reset-password`,
user
user,
);
if (res.status === 200) {
return res.data;
@ -577,7 +577,7 @@ export async function saveFlowStore(
last_tested_version?: string;
},
tags: string[],
publicFlow = false
publicFlow = false,
): Promise<FlowType> {
try {
const response = await api.post(`${BASE_URL_API}store/components/`, {
@ -706,7 +706,7 @@ export async function postStoreComponents(component: Component) {
export async function getComponent(component_id: string) {
try {
const res = await api.get(
`${BASE_URL_API}store/components/${component_id}`
`${BASE_URL_API}store/components/${component_id}`,
);
if (res.status === 200) {
return res.data;
@ -721,7 +721,7 @@ export async function searchComponent(
page?: number | null,
limit?: number | null,
status?: string | null,
tags?: string[]
tags?: string[],
): Promise<StoreComponentResponse | undefined> {
try {
let url = `${BASE_URL_API}store/components/`;
@ -833,7 +833,7 @@ export async function updateFlowStore(
},
tags: string[],
publicFlow = false,
id: string
id: string,
): Promise<FlowType> {
try {
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
@ -917,7 +917,7 @@ export async function deleteGlobalVariable(id: string) {
export async function updateGlobalVariable(
name: string,
value: string,
id: string
id: string,
) {
try {
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
@ -936,7 +936,7 @@ export async function getVerticesOrder(
startNodeId?: string | null,
stopNodeId?: string | null,
nodes?: Node[],
Edges?: Edge[]
Edges?: Edge[],
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
// nodeId is optional and is a query parameter
// if nodeId is not provided, the API will return all vertices
@ -956,19 +956,19 @@ export async function getVerticesOrder(
return await api.post(
`${BASE_URL_API}build/${flowId}/vertices`,
data,
config
config,
);
}
export async function postBuildVertex(
flowId: string,
vertexId: string,
input_value: string
input_value: string,
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
// input_value is optional and is a query parameter
return await api.post(
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
input_value ? { inputs: { input_value: input_value } } : undefined
input_value ? { inputs: { input_value: input_value } } : undefined,
);
}
@ -992,7 +992,7 @@ export async function getFlowPool({
}
export async function deleteFlowPool(
flowId: string
flowId: string,
): Promise<AxiosResponse<any>> {
const config = {};
config["params"] = { flow_id: flowId };
@ -1000,7 +1000,7 @@ export async function deleteFlowPool(
}
export async function multipleDeleteFlowsComponents(
flowIds: string[]
flowIds: string[],
): Promise<AxiosResponse<any>> {
return await api.post(`${BASE_URL_API}flows/multiple_delete/`, {
flow_ids: flowIds,
@ -1010,7 +1010,7 @@ export async function multipleDeleteFlowsComponents(
export async function getTransactionTable(
id: string,
mode: "intersection" | "union",
params = {}
params = {},
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
const config = {};
config["params"] = { flow_id: id };
@ -1025,7 +1025,7 @@ export async function getTransactionTable(
export async function getMessagesTable(
id: string,
mode: "intersection" | "union",
params = {}
params = {},
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
const config = {};
config["params"] = { flow_id: id };

View file

@ -89,7 +89,7 @@ export default function ParameterComponent({
debouncedHandleUpdateValues,
setNode,
renderTooltips,
setIsLoading
setIsLoading,
);
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
@ -98,7 +98,7 @@ export default function ParameterComponent({
takeSnapshot,
setNode,
updateNodeInternals,
renderTooltips
renderTooltips,
);
const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
@ -107,7 +107,7 @@ export default function ParameterComponent({
let disabled =
edges.some(
(edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id)
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
) ?? false;
const handleRefreshButtonPress = async (name, data) => {
@ -120,12 +120,12 @@ export default function ParameterComponent({
handleUpdateValues,
setNode,
renderTooltips,
setIsLoading
setIsLoading,
);
const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[],
skipSnapshot: boolean | undefined = false
skipSnapshot: boolean | undefined = false,
): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot);
};
@ -207,7 +207,7 @@ export default function ParameterComponent({
className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background",
!showNode ? "mt-0" : ""
!showNode ? "mt-0" : "",
)}
style={{
borderColor: color ?? nodeColors.unknown,
@ -296,7 +296,7 @@ export default function ParameterComponent({
}
className={classNames(
left ? "-ml-0.5" : "-mr-0.5",
"h-3 w-3 rounded-full border-2 bg-background"
"h-3 w-3 rounded-full border-2 bg-background",
)}
style={{ borderColor: color ?? nodeColors.unknown }}
onClick={() => setFilterEdge(groupedEdge.current)}

View file

@ -24,7 +24,7 @@ const TooltipRenderComponent = ({ item, index, left }) => {
<span
key={index}
className={classNames(
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center"
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center",
)}
>
<div

View file

@ -77,10 +77,10 @@ export default function GenericNode({
// first check if data.type in NATIVE_CATEGORIES
// if not return
if (!data.node?.template?.code?.value) return;
const thisNodeTemplate = templates[data.type].template;
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate.code) return;
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const componentsToIgnore = ["Custom Component"];
@ -416,6 +416,7 @@ export default function GenericNode({
"generic-node-title-arrangement rounded-full" +
(!showNode && " justify-center ")
}
data-testid="generic-node-title-arrangement"
>
{iconNodeRender()}
{showNode && (
@ -452,7 +453,7 @@ export default function GenericNode({
<div className="group flex items-start gap-1.5">
<ShadTooltip content={data.node?.display_name}>
<div
onDoubleClick={(event) => {
onClick={(event) => {
if (nameEditable) {
setInputName(true);
}
@ -466,21 +467,6 @@ export default function GenericNode({
{data.node?.display_name}
</div>
</ShadTooltip>
{nameEditable && (
<div
onClick={(event) => {
setInputName(true);
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
>
<IconComponent
name="PencilLine"
className="hidden h-3 w-3 text-status-blue group-hover:block"
/>
</div>
)}
</div>
)}
</div>
@ -718,14 +704,14 @@ export default function GenericNode({
) : (
<div
className={cn(
"generic-node-desc-text truncate-multiline word-break-break-word",
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
(data.node?.description === "" ||
!data.node?.description) &&
nameEditable
? "font-light italic"
: ""
)}
onDoubleClick={(e) => {
onClick={(e) => {
setInputDescription(true);
takeSnapshot();
}}

View file

@ -40,7 +40,7 @@ const useFetchDataOnMount = (
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);

View file

@ -44,7 +44,9 @@ const useHandleOnNewValue = (
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
list: [
responseError?.response?.data?.detail.error ?? "Unknown error",
],
});
}
setIsLoading(false);

View file

@ -6,7 +6,7 @@ const useHandleNodeClass = (
takeSnapshot,
setNode,
updateNodeInternals,
renderTooltips
renderTooltips,
) => {
const handleNodeClass = (newNodeClass, code) => {
if (!data.node) return;

View file

@ -26,7 +26,7 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);

View file

@ -3,7 +3,6 @@ import AccordionComponent from "../../components/accordionComponent";
import IconComponent from "../../components/genericIconComponent";
import ShadTooltip from "../../components/shadTooltipComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Tabs,
TabsContent,
@ -92,6 +91,7 @@ export default function IOModal({
await buildFlow({
input_value: chatValue,
startNodeId: chatInput?.id,
silent: true,
}).catch((err) => {
console.error(err);
setLockChat(false);
@ -121,6 +121,7 @@ export default function IOModal({
open={open}
setOpen={setOpen}
disable={disable}
onSubmit={() => sendMessage(1)}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
{/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */}
@ -253,6 +254,10 @@ export default function IOModal({
key={index}
>
<AccordionComponent
disabled={
node.data.node!.template["input_value"]
?.value === ""
}
trigger={
<div className="file-component-badge-div">
<ShadTooltip
@ -371,13 +376,10 @@ export default function IOModal({
</div>
</BaseModal.Content>
{!haveChat ? (
<BaseModal.Footer>
<div className="flex w-full justify-end pt-2">
<Button
variant={"outline"}
className="flex gap-2 px-3"
onClick={() => sendMessage(1)}
>
<BaseModal.Footer
submit={{
label: "Run Flow",
icon: (
<IconComponent
name={isBuilding ? "Loader2" : "Zap"}
className={cn(
@ -387,10 +389,9 @@ export default function IOModal({
: "fill-current text-medium-indigo",
)}
/>
Run Flow
</Button>
</div>
</BaseModal.Footer>
),
}}
/>
) : (
<></>
)}

View file

@ -8,14 +8,14 @@ export function getCurlRunCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject,
endpointName?: string
endpointName?: string,
): string {
const tweaksObject = tweaksBuildedObject[0];
// show the endpoint name in the curl command if it exists
return `curl -X POST \\
"${window.location.protocol}//${window.location.host}/api/v1/run/${
endpointName || flowId
}?stream=false" \\
endpointName || flowId
}?stream=false" \\
-H 'Content-Type: application/json'\\${
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
}

View file

@ -8,7 +8,7 @@
export default function getPythonApiCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject
tweaksBuildedObject,
): string {
const tweaksObject = tweaksBuildedObject[0];
return `import requests

View file

@ -6,7 +6,7 @@
*/
export default function getPythonCode(
flowName: string,
tweaksBuildedObject
tweaksBuildedObject,
): string {
const tweaksObject = tweaksBuildedObject[0];

View file

@ -1,7 +1,7 @@
export function createTabsArray(
codes,
includeWebhookCurl = false,
includeTweaks = false
includeTweaks = false,
) {
const tabs = [
{

View file

@ -35,7 +35,7 @@ const ApiModal = forwardRef(
flow: FlowType;
children: ReactNode;
},
ref
ref,
) => {
const tweak = useTweaksStore((state) => state.tweak);
const addTweaks = useTweaksStore((state) => state.setTweak);
@ -51,12 +51,12 @@ const ApiModal = forwardRef(
flow?.id,
autoLogin,
tweak,
flow?.endpoint_name
flow?.endpoint_name,
);
const curl_webhook_code = getCurlWebhookCode(
flow?.id,
autoLogin,
flow?.endpoint_name
flow?.endpoint_name,
);
const pythonCode = getPythonCode(flow?.name, tweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
@ -72,7 +72,7 @@ const ApiModal = forwardRef(
pythonCode,
];
const [tabs, setTabs] = useState(
createTabsArray(codesArray, includeWebhook)
createTabsArray(codesArray, includeWebhook),
);
const canShowTweaks =
@ -121,7 +121,7 @@ const ApiModal = forwardRef(
buildTweakObject(
nodeId,
element.data.node.template[templateField].value,
element.data.node.template[templateField]
element.data.node.template[templateField],
);
}
});
@ -138,7 +138,7 @@ const ApiModal = forwardRef(
async function buildTweakObject(
tw: string,
changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType
template: TemplateVariableType,
) {
changes = getChangesType(changes, template);
@ -180,7 +180,7 @@ const ApiModal = forwardRef(
flow?.id,
autoLogin,
cloneTweak,
flow?.endpoint_name
flow?.endpoint_name,
);
const pythonCode = getPythonCode(flow?.name, cloneTweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
@ -224,7 +224,7 @@ const ApiModal = forwardRef(
</BaseModal.Content>
</BaseModal>
);
}
},
);
export default ApiModal;

View file

@ -0,0 +1,67 @@
export const switchCaseModalSize = (size: string) => {
let minWidth: string;
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-full";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[11rem]";
break;
case "smaller-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "small":
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
height = "h-full";
break;
case "large":
minWidth = "min-w-[85vw]";
height = "h-[80vh]";
break;
case "three-cards":
minWidth = "min-w-[1066px]";
height = "h-fit";
break;
case "large-thin":
minWidth = "min-w-[65vw]";
height = "h-[80vh]";
break;
case "md-thin":
minWidth = "min-w-[85vw]";
height = "h-[70vh]";
break;
case "sm-thin":
minWidth = "min-w-[65vw]";
height = "h-[70vh]";
break;
case "large-h-full":
minWidth = "min-w-[80vw]";
height = "h-full";
break;
default:
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
}
return { minWidth, height };
};

View file

@ -15,8 +15,11 @@ import {
DialogContent as ModalContent,
} from "../../components/ui/dialog-with-no-close";
import { DialogClose } from "@radix-ui/react-dialog";
import { Button } from "../../components/ui/button";
import { modalHeaderType } from "../../types/components";
import { cn } from "../../utils/utils";
import { switchCaseModalSize } from "./helpers/switch-case-size";
type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string };
@ -61,8 +64,38 @@ const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
);
};
const Footer: React.FC<{ children: ReactNode }> = ({ children }) => {
return <>{children}</>;
const Footer: React.FC<{
children?: ReactNode;
submit?: {
label: string;
icon?: ReactNode;
loading?: boolean;
disabled?: boolean;
dataTestId?: string;
};
}> = ({ children, submit }) => {
return submit ? (
<div className="flex w-full items-center justify-between">
{children ?? <div />}
<div className="flex items-center gap-3">
<DialogClose asChild>
<Button variant="outline" type="button">
Cancel
</Button>
</DialogClose>
<Button
data-testid={submit.dataTestId}
type="submit"
loading={submit.loading}
>
{submit.icon && submit.icon}
{submit.label}
</Button>
</div>
</div>
) : (
<>{children && children}</>
);
};
interface BaseModalProps {
children: [
@ -91,6 +124,7 @@ interface BaseModalProps {
disable?: boolean;
onChangeOpenModal?: (open?: boolean) => void;
type?: "modal" | "dialog";
onSubmit?: () => void;
}
function BaseModal({
open,
@ -99,6 +133,7 @@ function BaseModal({
size = "large",
onChangeOpenModal,
type = "dialog",
onSubmit,
}: BaseModalProps) {
const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header,
@ -113,71 +148,7 @@ function BaseModal({
(child) => (child as React.ReactElement).type === Footer,
);
let minWidth: string;
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-full";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[11rem]";
break;
case "smaller-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "small":
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
height = "h-full";
break;
case "large":
minWidth = "min-w-[85vw]";
height = "h-[80vh]";
break;
case "three-cards":
minWidth = "min-w-[1066px]";
height = "h-fit";
break;
case "large-thin":
minWidth = "min-w-[65vw]";
height = "h-[80vh]";
break;
case "md-thin":
minWidth = "min-w-[85vw]";
height = "h-[70vh]";
break;
case "sm-thin":
minWidth = "min-w-[65vw]";
height = "h-[70vh]";
break;
case "large-h-full":
minWidth = "min-w-[80vw]";
height = "h-full";
break;
default:
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
}
let { minWidth, height } = switchCaseModalSize(size);
useEffect(() => {
if (onChangeOpenModal) {
@ -212,13 +183,34 @@ function BaseModal({
<div className="truncate-doubleline word-break-break-word">
{headerChild}
</div>
<div
className={`flex flex-col ${height} w-full transition-all duration-300`}
>
{ContentChild}
</div>
{ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div>
{onSubmit ? (
<form
onSubmit={(event) => {
event.preventDefault();
onSubmit();
}}
className="flex flex-col gap-6"
>
<div
className={`flex flex-col ${height} w-full transition-all duration-300`}
>
{ContentChild}
</div>
{ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div>
)}
</form>
) : (
<>
<div
className={`flex flex-col ${height} w-full transition-all duration-300`}
>
{ContentChild}
</div>
{ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div>
)}
</>
)}
</DialogContent>
</Dialog>

View file

@ -59,7 +59,7 @@ export default function DeleteConfirmationModal({
<DialogClose asChild>
<Button
onClick={(e) => e.stopPropagation()}
className="mr-3"
className="mr-1"
variant="outline"
>
Cancel

View file

@ -15,7 +15,6 @@ import ShadTooltip from "../../components/shadTooltipComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Table,
TableBody,
@ -102,6 +101,16 @@ const EditNodeModal = forwardRef(
onChangeOpenModal={(open) => {
setMyData(data);
}}
onSubmit={() => {
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: myData.node,
},
}));
setOpen(false);
}}
>
<BaseModal.Trigger>
<></>
@ -134,6 +143,7 @@ const EditNodeModal = forwardRef(
<TableHeader className="edit-node-modal-table-header">
<TableRow className="">
<TableHead className="h-7 text-center">PARAM</TableHead>
<TableHead className="h-7 text-center">DESC</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
@ -188,11 +198,17 @@ const EditNodeModal = forwardRef(
>
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
<ShadTooltip
styleClasses="z-50"
content={
myData.node?.template[templateParam].proxy
? myData.node?.template[templateParam]
.proxy?.id
: null
: myData.node?.template[templateParam]
.display_name
? myData.node!.template[templateParam]
.display_name
: myData.node?.template[templateParam]
.name
}
>
<span>
@ -205,6 +221,20 @@ const EditNodeModal = forwardRef(
</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
<ShadTooltip
styleClasses="z-50"
content={
data.node?.template[templateParam]?.info ??
null
}
>
<span>
{data.node?.template[templateParam]?.info ??
""}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
<Case
condition={
@ -610,26 +640,7 @@ const EditNodeModal = forwardRef(
</div>
</BaseModal.Content>
<BaseModal.Footer>
<Button
data-test-id="saveChangesBtn"
id={"saveChangesBtn"}
className="mt-3"
onClick={() => {
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: myData.node,
},
}));
setOpen(false);
}}
type="submit"
>
Save Changes
</Button>
</BaseModal.Footer>
<BaseModal.Footer submit={{ label: "Save Changes" }} />
</BaseModal>
);
}

Some files were not shown because too many files have changed in this diff Show more