Feat: Implement Customizable Shortcuts
This pull request improve user experience by enabling customization of keyboard shortcuts.
This commit is contained in:
commit
20e7b82ba4
42 changed files with 1587 additions and 684 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Agents
|
# Agents
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
|
We appreciate your understanding as we polish our documentation – it may
|
||||||
</p>
|
contain some rough edges. Share your feedback or report issues to help us
|
||||||
|
improve! 🛠️📝
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
Agents are components that use reasoning to make decisions and take actions, designed to autonomously perform tasks or provide services with some degree of agency. LLM chains can only perform hardcoded sequences of actions, while agents use LLMs to reason through which actions to take, and in which order.
|
Agents are components that use reasoning to make decisions and take actions, designed to autonomously perform tasks or provide services with some degree of agency. LLM chains can only perform hardcoded sequences of actions, while agents use LLMs to reason through which actions to take, and in which order.
|
||||||
|
|
@ -87,4 +89,4 @@ The `ZeroShotAgent` uses the ReAct framework to decide which tool to use based o
|
||||||
**Parameters**:
|
**Parameters**:
|
||||||
|
|
||||||
- **Allowed Tools:** The tools accessible to the agent.
|
- **Allowed Tools:** The tools accessible to the agent.
|
||||||
- **LLM Chain:** The LLM Chain used by the agent.
|
- **LLM Chain:** The LLM Chain used by the agent.
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import Admonition from "@theme/Admonition";
|
||||||
# Chains
|
# Chains
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
Thank you for your patience while we enhance our documentation. It may
|
Thank you for your patience while we enhance our documentation. It may have
|
||||||
have some imperfections. Share your feedback or report issues to help us
|
some imperfections. Share your feedback or report issues to help us improve!
|
||||||
improve! 🛠️📝
|
🛠️📝
|
||||||
</p>
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
Chains, in the context of language models, refer to a series of calls made to a language model. This approach allows for using the output of one call as the input for another. Different chain types facilitate varying complexity levels, making them useful for creating pipelines and executing specific scenarios.
|
Chains, in the context of language models, refer to a series of calls made to a language model. This approach allows for using the output of one call as the input for another. Different chain types facilitate varying complexity levels, making them useful for creating pipelines and executing specific scenarios.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,113 +4,113 @@
|
||||||
|
|
||||||
Used to load embedding models from [Amazon Bedrock](https://aws.amazon.com/bedrock/).
|
Used to load embedding models from [Amazon Bedrock](https://aws.amazon.com/bedrock/).
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|-----------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------|-------------|
|
| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
| `credentials_profile_name` | `str` | Name of the AWS credentials profile in ~/.aws/credentials or ~/.aws/config, which has access keys or role information. | |
|
| `credentials_profile_name` | `str` | Name of the AWS credentials profile in ~/.aws/credentials or ~/.aws/config, which has access keys or role information. | |
|
||||||
| `model_id` | `str` | ID of the model to call, e.g., `amazon.titan-embed-text-v1`. This is equivalent to the `modelId` property in the `list-foundation-models` API. | |
|
| `model_id` | `str` | ID of the model to call, e.g., `amazon.titan-embed-text-v1`. This is equivalent to the `modelId` property in the `list-foundation-models` API. | |
|
||||||
| `endpoint_url` | `str` | URL to set a specific service endpoint other than the default AWS endpoint. | |
|
| `endpoint_url` | `str` | URL to set a specific service endpoint other than the default AWS endpoint. | |
|
||||||
| `region_name` | `str` | AWS region to use, e.g., `us-west-2`. Falls back to `AWS_DEFAULT_REGION` environment variable or region specified in ~/.aws/config if not provided. | |
|
| `region_name` | `str` | AWS region to use, e.g., `us-west-2`. Falls back to `AWS_DEFAULT_REGION` environment variable or region specified in ~/.aws/config if not provided. | |
|
||||||
|
|
||||||
## Cohere Embeddings
|
## Cohere Embeddings
|
||||||
|
|
||||||
Used to load embedding models from [Cohere](https://cohere.com/).
|
Used to load embedding models from [Cohere](https://cohere.com/).
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|---------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------|
|
| ---------------- | -------- | ------------------------------------------------------------------------- | -------------------- |
|
||||||
| `cohere_api_key` | `str` | API key required to authenticate with the Cohere service. | |
|
| `cohere_api_key` | `str` | API key required to authenticate with the Cohere service. | |
|
||||||
| `model` | `str` | Language model used for embedding text documents and performing queries. | `embed-english-v2.0` |
|
| `model` | `str` | Language model used for embedding text documents and performing queries. | `embed-english-v2.0` |
|
||||||
| `truncate` | `bool` | Whether to truncate the input text to fit within the model's constraints. | `False` |
|
| `truncate` | `bool` | Whether to truncate the input text to fit within the model's constraints. | `False` |
|
||||||
|
|
||||||
## Azure OpenAI Embeddings
|
## Azure OpenAI Embeddings
|
||||||
|
|
||||||
Generate embeddings using Azure OpenAI models.
|
Generate embeddings using Azure OpenAI models.
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|---------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------|
|
| ----------------- | -------- | -------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
| `Azure Endpoint` | `str` | Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/` | |
|
| `Azure Endpoint` | `str` | Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/` | |
|
||||||
| `Deployment Name` | `str` | The name of the deployment. | |
|
| `Deployment Name` | `str` | The name of the deployment. | |
|
||||||
| `API Version` | `str` | The API version to use, options include various dates. | |
|
| `API Version` | `str` | The API version to use, options include various dates. | |
|
||||||
| `API Key` | `str` | The API key to access the Azure OpenAI service. | |
|
| `API Key` | `str` | The API key to access the Azure OpenAI service. | |
|
||||||
|
|
||||||
## Hugging Face API Embeddings
|
## Hugging Face API Embeddings
|
||||||
|
|
||||||
Generate embeddings using Hugging Face Inference API models.
|
Generate embeddings using Hugging Face Inference API models.
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|---------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------|
|
| --------------- | -------- | ----------------------------------------------------- | ------------------------ |
|
||||||
| `API Key` | `str` | API key for accessing the Hugging Face Inference API. | |
|
| `API Key` | `str` | API key for accessing the Hugging Face Inference API. | |
|
||||||
| `API URL` | `str` | URL of the Hugging Face Inference API. | `http://localhost:8080` |
|
| `API URL` | `str` | URL of the Hugging Face Inference API. | `http://localhost:8080` |
|
||||||
| `Model Name` | `str` | Name of the model to use for embeddings. | `BAAI/bge-large-en-v1.5` |
|
| `Model Name` | `str` | Name of the model to use for embeddings. | `BAAI/bge-large-en-v1.5` |
|
||||||
| `Cache Folder` | `str` | Folder path to cache Hugging Face models. | |
|
| `Cache Folder` | `str` | Folder path to cache Hugging Face models. | |
|
||||||
| `Encode Kwargs` | `dict` | Additional arguments for the encoding process. | |
|
| `Encode Kwargs` | `dict` | Additional arguments for the encoding process. | |
|
||||||
| `Model Kwargs` | `dict` | Additional arguments for the model. | |
|
| `Model Kwargs` | `dict` | Additional arguments for the model. | |
|
||||||
| `Multi Process` | `bool` | Whether to use multiple processes. | `False` |
|
| `Multi Process` | `bool` | Whether to use multiple processes. | `False` |
|
||||||
|
|
||||||
## Hugging Face Embeddings
|
## Hugging Face Embeddings
|
||||||
|
|
||||||
Used to load embedding models from [HuggingFace](https://huggingface.co).
|
Used to load embedding models from [HuggingFace](https://huggingface.co).
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|---------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------|
|
| --------------- | -------- | ---------------------------------------------- | ----------------------------------------- |
|
||||||
| `Cache Folder` | `str` | Folder path to cache HuggingFace models. | |
|
| `Cache Folder` | `str` | Folder path to cache HuggingFace models. | |
|
||||||
| `Encode Kwargs` | `dict` | Additional arguments for the encoding process. | |
|
| `Encode Kwargs` | `dict` | Additional arguments for the encoding process. | |
|
||||||
| `Model Kwargs` | `dict` | Additional arguments for the model. | |
|
| `Model Kwargs` | `dict` | Additional arguments for the model. | |
|
||||||
| `Model Name` | `str` | Name of the HuggingFace model to use. | `sentence-transformers/all-mpnet-base-v2` |
|
| `Model Name` | `str` | Name of the HuggingFace model to use. | `sentence-transformers/all-mpnet-base-v2` |
|
||||||
| `Multi Process` | `bool` | Whether to use multiple processes. | `False` |
|
| `Multi Process` | `bool` | Whether to use multiple processes. | `False` |
|
||||||
|
|
||||||
## OpenAI Embeddings
|
## OpenAI Embeddings
|
||||||
|
|
||||||
Used to load embedding models from [OpenAI](https://openai.com/).
|
Used to load embedding models from [OpenAI](https://openai.com/).
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|-----------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
|
| -------------------------- | ---------------- | ------------------------------------------------ | ------------------------ |
|
||||||
| `OpenAI API Key` | `str` | The API key to use for accessing the OpenAI API. | |
|
| `OpenAI API Key` | `str` | The API key to use for accessing the OpenAI API. | |
|
||||||
| `Default Headers` | `Dict[str, str]` | Default headers for the HTTP requests. | |
|
| `Default Headers` | `Dict[str, str]` | Default headers for the HTTP requests. | |
|
||||||
| `Default Query` | `NestedDict` | Default query parameters for the HTTP requests. | |
|
| `Default Query` | `NestedDict` | Default query parameters for the HTTP requests. | |
|
||||||
| `Allowed Special` | `List[str]` | Special tokens allowed for processing. | `[]` |
|
| `Allowed Special` | `List[str]` | Special tokens allowed for processing. | `[]` |
|
||||||
| `Disallowed Special` | `List[str]` | Special tokens disallowed for processing. | `["all"]` |
|
| `Disallowed Special` | `List[str]` | Special tokens disallowed for processing. | `["all"]` |
|
||||||
| `Chunk Size` | `int` | Chunk size for processing. | `1000` |
|
| `Chunk Size` | `int` | Chunk size for processing. | `1000` |
|
||||||
| `Client` | `Any` | HTTP client for making requests. | |
|
| `Client` | `Any` | HTTP client for making requests. | |
|
||||||
| `Deployment` | `str` | Deployment name for the model. | `text-embedding-3-small` |
|
| `Deployment` | `str` | Deployment name for the model. | `text-embedding-3-small` |
|
||||||
| `Embedding Context Length` | `int` | Length of embedding context. | `8191` |
|
| `Embedding Context Length` | `int` | Length of embedding context. | `8191` |
|
||||||
| `Max Retries` | `int` | Maximum number of retries for failed requests. | `6` |
|
| `Max Retries` | `int` | Maximum number of retries for failed requests. | `6` |
|
||||||
| `Model` | `str` | Name of the model to use. | `text-embedding-3-small` |
|
| `Model` | `str` | Name of the model to use. | `text-embedding-3-small` |
|
||||||
| `Model Kwargs` | `NestedDict` | Additional keyword arguments for the model. | |
|
| `Model Kwargs` | `NestedDict` | Additional keyword arguments for the model. | |
|
||||||
| `OpenAI API Base` | `str` | Base URL of the OpenAI API. | |
|
| `OpenAI API Base` | `str` | Base URL of the OpenAI API. | |
|
||||||
| `OpenAI API Type` | `str` | Type of the OpenAI API. | |
|
| `OpenAI API Type` | `str` | Type of the OpenAI API. | |
|
||||||
| `OpenAI API Version` | `str` | Version of the OpenAI API. | |
|
| `OpenAI API Version` | `str` | Version of the OpenAI API. | |
|
||||||
| `OpenAI Organization` | `str` | Organization associated with the API key. | |
|
| `OpenAI Organization` | `str` | Organization associated with the API key. | |
|
||||||
| `OpenAI Proxy` | `str` | Proxy server for the requests. | |
|
| `OpenAI Proxy` | `str` | Proxy server for the requests. | |
|
||||||
| `Request Timeout` | `float` | Timeout for the HTTP requests. | |
|
| `Request Timeout` | `float` | Timeout for the HTTP requests. | |
|
||||||
| `Show Progress Bar` | `bool` | Whether to show a progress bar for processing. | `False` |
|
| `Show Progress Bar` | `bool` | Whether to show a progress bar for processing. | `False` |
|
||||||
| `Skip Empty` | `bool` | Whether to skip empty inputs. | `False` |
|
| `Skip Empty` | `bool` | Whether to skip empty inputs. | `False` |
|
||||||
| `TikToken Enable` | `bool` | Whether to enable TikToken. | `True` |
|
| `TikToken Enable` | `bool` | Whether to enable TikToken. | `True` |
|
||||||
| `TikToken Model Name` | `str` | Name of the TikToken model. | |
|
| `TikToken Model Name` | `str` | Name of the TikToken model. | |
|
||||||
|
|
||||||
## Ollama Embeddings
|
## Ollama Embeddings
|
||||||
|
|
||||||
Generate embeddings using Ollama models.
|
Generate embeddings using Ollama models.
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|---------------------|-------------------|--------------------------------------------------------------------------------------------------------------------|---------------------------|
|
| ------------------- | -------- | ---------------------------------------------------------------------------------------- | ------------------------ |
|
||||||
| `Ollama Model` | `str` | Name of the Ollama model to use. | `llama2` |
|
| `Ollama Model` | `str` | Name of the Ollama model to use. | `llama2` |
|
||||||
| `Ollama Base URL` | `str` | Base URL of the Ollama API. | `http://localhost:11434` |
|
| `Ollama Base URL` | `str` | Base URL of the Ollama API. | `http://localhost:11434` |
|
||||||
| `Model Temperature` | `float` | Temperature parameter for the model. Adjusts the randomness in the generated embeddings. | |
|
| `Model Temperature` | `float` | Temperature parameter for the model. Adjusts the randomness in the generated embeddings. | |
|
||||||
|
|
||||||
## VertexAI Embeddings
|
## VertexAI Embeddings
|
||||||
|
|
||||||
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) [Embeddings API](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings).
|
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) [Embeddings API](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings).
|
||||||
|
|
||||||
| **Parameter** | **Type** | **Description** | **Default** |
|
| **Parameter** | **Type** | **Description** | **Default** |
|
||||||
|-----------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
|
| --------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||||
| `credentials` | `Credentials` | The default custom credentials to use. | |
|
| `credentials` | `Credentials` | The default custom credentials to use. | |
|
||||||
| `location` | `str` | The default location to use when making API calls. | `us-central1`|
|
| `location` | `str` | The default location to use when making API calls. | `us-central1` |
|
||||||
| `max_output_tokens` | `int` | Token limit determines the maximum amount of text output from one prompt. | `128` |
|
| `max_output_tokens` | `int` | Token limit determines the maximum amount of text output from one prompt. | `128` |
|
||||||
| `model_name` | `str` | The name of the Vertex AI large language model. | `text-bison`|
|
| `model_name` | `str` | The name of the Vertex AI large language model. | `text-bison` |
|
||||||
| `project` | `str` | The default GCP project to use when making Vertex API calls. | |
|
| `project` | `str` | The default GCP project to use when making Vertex API calls. | |
|
||||||
| `request_parallelism` | `int` | The amount of parallelism allowed for requests issued to VertexAI models. | `5` |
|
| `request_parallelism` | `int` | The amount of parallelism allowed for requests issued to VertexAI models. | `5` |
|
||||||
| `temperature` | `float` | Tunes the degree of randomness in text generations. Should be a non-negative value. | `0` |
|
| `temperature` | `float` | Tunes the degree of randomness in text generations. Should be a non-negative value. | `0` |
|
||||||
| `top_k` | `int` | How the model selects tokens for output, the next token is selected from the top `k` tokens. | `40` |
|
| `top_k` | `int` | How the model selects tokens for output, the next token is selected from the top `k` tokens. | `40` |
|
||||||
| `top_p` | `float` | Tokens are selected from the most probable to least until the sum of their probabilities exceeds the top `p` value. | `0.95` |
|
| `top_p` | `float` | Tokens are selected from the most probable to least until the sum of their probabilities exceeds the top `p` value. | `0.95` |
|
||||||
| `tuned_model_name` | `str` | The name of a tuned model. If provided, `model_name` is ignored. | |
|
| `tuned_model_name` | `str` | The name of a tuned model. If provided, `model_name` is ignored. | |
|
||||||
| `verbose` | `bool` | This parameter controls the level of detail in the output. When set to `True`, it prints internal states of the chain to help debug. | `False` |
|
| `verbose` | `bool` | This parameter controls the level of detail in the output. When set to `True`, it prints internal states of the chain to help debug. | `False` |
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Experimental
|
# Experimental
|
||||||
|
|
||||||
|
|
@ -29,10 +29,12 @@ This component extracts specified keys from a record.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Record:**
|
- **Record:**
|
||||||
|
|
||||||
- **Display Name:** Record
|
- **Display Name:** Record
|
||||||
- **Info:** The record from which to extract keys.
|
- **Info:** The record from which to extract keys.
|
||||||
|
|
||||||
- **Keys:**
|
- **Keys:**
|
||||||
|
|
||||||
- **Display Name:** Keys
|
- **Display Name:** Keys
|
||||||
- **Info:** The keys to be extracted.
|
- **Info:** The keys to be extracted.
|
||||||
|
|
||||||
|
|
@ -54,6 +56,7 @@ This component turns a function running a flow into a Tool.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Flow Name:**
|
- **Flow Name:**
|
||||||
|
|
||||||
- **Display Name:** Flow Name
|
- **Display Name:** Flow Name
|
||||||
- **Info:** Select the flow to run.
|
- **Info:** Select the flow to run.
|
||||||
- **Options:** List of available flows.
|
- **Options:** List of available flows.
|
||||||
|
|
@ -61,10 +64,12 @@ This component turns a function running a flow into a Tool.
|
||||||
- **Refresh Button:** True
|
- **Refresh Button:** True
|
||||||
|
|
||||||
- **Name:**
|
- **Name:**
|
||||||
|
|
||||||
- **Display Name:** Name
|
- **Display Name:** Name
|
||||||
- **Description:** The tool's name.
|
- **Description:** The tool's name.
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
||||||
- **Display Name:** Description
|
- **Display Name:** Description
|
||||||
- **Description:** Describes the tool.
|
- **Description:** Describes the tool.
|
||||||
|
|
||||||
|
|
@ -127,10 +132,12 @@ This component generates a notification.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Name:**
|
- **Name:**
|
||||||
|
|
||||||
- **Display Name:** Name
|
- **Display Name:** Name
|
||||||
- **Info:** The notification's name.
|
- **Info:** The notification's name.
|
||||||
|
|
||||||
- **Record:**
|
- **Record:**
|
||||||
|
|
||||||
- **Display Name:** Record
|
- **Display Name:** Record
|
||||||
- **Info:** Optionally, a record to store in the notification.
|
- **Info:** Optionally, a record to store in the notification.
|
||||||
|
|
||||||
|
|
@ -151,10 +158,12 @@ This component runs a specified flow.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Input Value:**
|
- **Input Value:**
|
||||||
|
|
||||||
- **Display Name:** Input Value
|
- **Display Name:** Input Value
|
||||||
- **Multiline:** True
|
- **Multiline:** True
|
||||||
|
|
||||||
- **Flow Name:**
|
- **Flow Name:**
|
||||||
|
|
||||||
- **Display Name:** Flow Name
|
- **Display Name:** Flow Name
|
||||||
- **Info:** Select the flow to run.
|
- **Info:** Select the flow to run.
|
||||||
- **Options:** List of available flows.
|
- **Options:** List of available flows.
|
||||||
|
|
@ -177,14 +186,17 @@ This component executes a specified runnable.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Input Key:**
|
- **Input Key:**
|
||||||
|
|
||||||
- **Display Name:** Input Key
|
- **Display Name:** Input Key
|
||||||
- **Info:** The input key.
|
- **Info:** The input key.
|
||||||
|
|
||||||
- **Inputs:**
|
- **Inputs:**
|
||||||
|
|
||||||
- **Display Name:** Inputs
|
- **Display Name:** Inputs
|
||||||
- **Info:** Inputs for the runnable.
|
- **Info:** Inputs for the runnable.
|
||||||
|
|
||||||
- **Runnable:**
|
- **Runnable:**
|
||||||
|
|
||||||
- **Display Name:** Runnable
|
- **Display Name:** Runnable
|
||||||
- **Info:** The runnable to execute.
|
- **Info:** The runnable to execute.
|
||||||
|
|
||||||
|
|
@ -205,14 +217,17 @@ This component executes an SQL query.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Database URL:**
|
- **Database URL:**
|
||||||
|
|
||||||
- **Display Name:** Database URL
|
- **Display Name:** Database URL
|
||||||
- **Info:** The database's URL.
|
- **Info:** The database's URL.
|
||||||
|
|
||||||
- **Include Columns:**
|
- **Include Columns:**
|
||||||
|
|
||||||
- **Display Name:** Include Columns
|
- **Display Name:** Include Columns
|
||||||
- **Info:** Whether to include columns in the result.
|
- **Info:** Whether to include columns in the result.
|
||||||
|
|
||||||
- **Passthrough:**
|
- **Passthrough:**
|
||||||
|
|
||||||
- **Display Name:** Passthrough
|
- **Display Name:** Passthrough
|
||||||
- **Info:** Returns the query instead of raising an exception if an error occurs.
|
- **Info:** Returns the query instead of raising an exception if an error occurs.
|
||||||
|
|
||||||
|
|
@ -233,10 +248,12 @@ This component dynamically generates a tool from a flow.
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- **Input Value:**
|
- **Input Value:**
|
||||||
|
|
||||||
- **Display Name:** Input Value
|
- **Display Name:** Input Value
|
||||||
- **Multiline:** True
|
- **Multiline:** True
|
||||||
|
|
||||||
- **Flow Name:**
|
- **Flow Name:**
|
||||||
|
|
||||||
- **Display Name:** Flow Name
|
- **Display Name:** Flow Name
|
||||||
- **Info:** Select the flow to run.
|
- **Info:** Select the flow to run.
|
||||||
- **Options:** List of available flows.
|
- **Options:** List of available flows.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
|
|
@ -49,9 +49,10 @@ Use this component as a template to create your custom component.
|
||||||
- **Parameter:** Describe the purpose of this parameter.
|
- **Parameter:** Describe the purpose of this parameter.
|
||||||
|
|
||||||
<Admonition type="info" title="Info">
|
<Admonition type="info" title="Info">
|
||||||
<p>
|
<p>
|
||||||
Customize the <code>build_config</code> and <code>build</code> methods according to your requirements.
|
Customize the <code>build_config</code> and <code>build</code> methods
|
||||||
</p>
|
according to your requirements.
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
Learn more about creating custom components at [Custom Component](http://docs.langflow.org/components/custom).
|
Learn more about creating custom components at [Custom Component](http://docs.langflow.org/components/custom).
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Memories
|
# Memories
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
Thanks for your patience as we improve our documentation—it might have some rough edges. Share your feedback or report issues to help us enhance it! 🛠️📝
|
Thanks for your patience as we improve our documentation—it might have some
|
||||||
</p>
|
rough edges. Share your feedback or report issues to help us enhance it!
|
||||||
|
🛠️📝
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
Memory is a concept in chat-based applications that allows the system to remember previous interactions. This capability helps maintain the context of the conversation and enables the system to understand new messages in light of past messages.
|
Memory is a concept in chat-based applications that allows the system to remember previous interactions. This capability helps maintain the context of the conversation and enables the system to understand new messages in light of past messages.
|
||||||
|
|
@ -24,9 +26,13 @@ This component retrieves stored messages using various filters such as sender ty
|
||||||
- **number_of_messages**: Specifies the number of messages to retrieve. Defaults to `5`. Determines the number of recent messages from the chat history to fetch.
|
- **number_of_messages**: Specifies the number of messages to retrieve. Defaults to `5`. Determines the number of recent messages from the chat history to fetch.
|
||||||
|
|
||||||
<Admonition type="note" title="Note">
|
<Admonition type="note" title="Note">
|
||||||
<p>
|
<p>
|
||||||
The component retrieves messages based on the provided criteria, including the specific file path for stored messages. If no specific criteria are provided, it returns the most recent messages up to the specified limit. This component can be used to review past interactions and analyze conversation flows.
|
The component retrieves messages based on the provided criteria, including
|
||||||
</p>
|
the specific file path for stored messages. If no specific criteria are
|
||||||
|
provided, it returns the most recent messages up to the specified limit.
|
||||||
|
This component can be used to review past interactions and analyze
|
||||||
|
conversation flows.
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
### ConversationBufferMemory
|
### ConversationBufferMemory
|
||||||
|
|
@ -84,7 +90,8 @@ The `ConversationKGMemory` utilizes a knowledge graph to enhance memory capabili
|
||||||
- **memory_key**: Specifies the prompt variable name where the memory stores and retrieves chat messages. Defaults to `chat_history`.
|
- **memory_key**: Specifies the prompt variable name where the memory stores and retrieves chat messages. Defaults to `chat_history`.
|
||||||
- **output_key**: Identifies the key under which the generated response
|
- **output_key**: Identifies the key under which the generated response
|
||||||
|
|
||||||
is stored, enabling retrieval using this key.
|
is stored, enabling retrieval using this key.
|
||||||
|
|
||||||
- **return_messages**: Controls whether the history is returned as a string or as a list of messages. Defaults to `False`.
|
- **return_messages**: Controls whether the history is returned as a string or as a list of messages. Defaults to `False`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -124,4 +131,4 @@ The `VectorRetrieverMemory` retrieves vectors based on queries, facilitating vec
|
||||||
- **Retriever**: The tool used to fetch documents.
|
- **Retriever**: The tool used to fetch documents.
|
||||||
- **input_key**: Identifies where input messages are stored in the memory object, facilitating their retrieval and manipulation.
|
- **input_key**: Identifies where input messages are stored in the memory object, facilitating their retrieval and manipulation.
|
||||||
- **memory_key**: Specifies the prompt variable name where the memory stores and retrieves chat messages. Defaults to `chat_history`.
|
- **memory_key**: Specifies the prompt variable name where the memory stores and retrieves chat messages. Defaults to `chat_history`.
|
||||||
- **return_messages**: Controls whether the history is returned as a string or as a list of messages. Defaults to `False`.
|
- **return_messages**: Controls whether the history is returned as a string or as a list of messages. Defaults to `False`.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Large Language Models (LLMs)
|
# Large Language Models (LLMs)
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
Thank you for your patience as we refine our documentation. You might encounter some inconsistencies. Please help us improve by sharing your feedback or reporting any issues! 🛠️📝
|
Thank you for your patience as we refine our documentation. You might
|
||||||
</p>
|
encounter some inconsistencies. Please help us improve by sharing your
|
||||||
|
feedback or reporting any issues! 🛠️📝
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
A Large Language Model (LLM) is a foundational component of Langflow. It provides a uniform interface for interacting with LLMs from various providers, including OpenAI, Cohere, and HuggingFace. Langflow extensively uses LLMs across its chains and agents, employing them to generate text based on specific prompts or inputs.
|
A Large Language Model (LLM) is a foundational component of Langflow. It provides a uniform interface for interacting with LLMs from various providers, including OpenAI, Cohere, and HuggingFace. Langflow extensively uses LLMs across its chains and agents, employing them to generate text based on specific prompts or inputs.
|
||||||
|
|
@ -37,7 +39,9 @@ This is a wrapper for Anthropic's large language model designed for chat-based i
|
||||||
`CTransformers` provides access to Transformer models implemented in C/C++ using the [GGML](https://github.com/ggerganov/ggml) library.
|
`CTransformers` provides access to Transformer models implemented in C/C++ using the [GGML](https://github.com/ggerganov/ggml) library.
|
||||||
|
|
||||||
<Admonition type="info">
|
<Admonition type="info">
|
||||||
Ensure the `ctransformers` Python package is installed. Discover more about installation, supported models, and usage [here](https://github.com/marella/ctransformers).
|
Ensure the `ctransformers` Python package is installed. Discover more about
|
||||||
|
installation, supported models, and usage
|
||||||
|
[here](https://github.com/marella/ctransformers).
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
- **config:** This configuration is for the Transformer models. Check the default settings and possible configurations at [config](https://github.com/marella/ctransformers#config).
|
- **config:** This configuration is for the Transformer models. Check the default settings and possible configurations at [config](https://github.com/marella/ctransformers#config).
|
||||||
|
|
@ -128,7 +132,8 @@ This component integrates with [Google Vertex AI](https://cloud.google.com/verte
|
||||||
|
|
||||||
- **credentials**: Custom
|
- **credentials**: Custom
|
||||||
|
|
||||||
credentials used for API interactions.
|
credentials used for API interactions.
|
||||||
|
|
||||||
- **location**: The default location for API calls, defaulting to `us-central1`.
|
- **location**: The default location for API calls, defaulting to `us-central1`.
|
||||||
- **max_output_tokens**: Limits the output tokens per prompt, defaulting to `128`.
|
- **max_output_tokens**: Limits the output tokens per prompt, defaulting to `128`.
|
||||||
- **model_name**: The name of the Vertex AI model in use, defaulting to `text-bison`.
|
- **model_name**: The name of the Vertex AI model in use, defaulting to `text-bison`.
|
||||||
|
|
@ -140,4 +145,4 @@ This component integrates with [Google Vertex AI](https://cloud.google.com/verte
|
||||||
- **tuned_model_name**: Specifies a tuned model name, which overrides the default model name if provided.
|
- **tuned_model_name**: Specifies a tuned model name, which overrides the default model name if provided.
|
||||||
- **verbose**: Controls the output verbosity to assist in debugging and understanding the operational details, defaulting to `False`.
|
- **verbose**: Controls the output verbosity to assist in debugging and understanding the operational details, defaulting to `False`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Retrievers
|
# Retrievers
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
We appreciate your patience as we enhance our documentation. It may have some imperfections. Please share your feedback or report issues to help us improve. 🛠️📝
|
We appreciate your patience as we enhance our documentation. It may have
|
||||||
</p>
|
some imperfections. Please share your feedback or report issues to help us
|
||||||
|
improve. 🛠️📝
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
A retriever is an interface that returns documents in response to an unstructured query. It's broader than a vector store because it doesn't need to store documents; it only needs to retrieve them.
|
A retriever is an interface that returns documents in response to an unstructured query. It's broader than a vector store because it doesn't need to store documents; it only needs to retrieve them.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
Thanks for your patience as we refine our documentation. It might have some rough edges currently. Please share your feedback or report issues to help us enhance it! 🛠️📝
|
Thanks for your patience as we refine our documentation. It might have some
|
||||||
</p>
|
rough edges currently. Please share your feedback or report issues to help
|
||||||
|
us enhance it! 🛠️📝
|
||||||
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
### SearchApi
|
### SearchApi
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import Admonition from "@theme/Admonition";
|
||||||
# Utilities
|
# Utilities
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="Zone Under Construction">
|
<Admonition type="caution" icon="🚧" title="Zone Under Construction">
|
||||||
We appreciate your understanding as we polish our documentation—it may
|
We appreciate your understanding as we polish our documentation—it may contain
|
||||||
contain some rough edges. Share your feedback or report issues to help us
|
some rough edges. Share your feedback or report issues to help us improve!
|
||||||
improve! 🛠️📝
|
🛠️📝
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
Utilities are a set of actions that can be used to perform common tasks in a flow. They are available in the **Utilities** section in the sidebar.
|
Utilities are a set of actions that can be used to perform common tasks in a flow. They are available in the **Utilities** section in the sidebar.
|
||||||
|
|
@ -86,7 +86,11 @@ Generates a unique identifier (UUID) for each instance it is invoked, providing
|
||||||
- Returns a unique identifier (UUID) as a string. This UUID is generated using Python's `uuid` module, ensuring that each identifier is unique and can be used as a reliable reference in your application.
|
- Returns a unique identifier (UUID) as a string. This UUID is generated using Python's `uuid` module, ensuring that each identifier is unique and can be used as a reliable reference in your application.
|
||||||
|
|
||||||
<Admonition type="note" title="Note">
|
<Admonition type="note" title="Note">
|
||||||
The Unique ID Generator is crucial for scenarios requiring distinct identifiers, such as session management, transaction tracking, or any context where different instances or entities must be uniquely identified. The generated UUID is provided as a hexadecimal string, offering a high level of uniqueness and security for identification purposes.
|
The Unique ID Generator is crucial for scenarios requiring distinct
|
||||||
|
identifiers, such as session management, transaction tracking, or any context
|
||||||
|
where different instances or entities must be uniquely identified. The
|
||||||
|
generated UUID is provided as a hexadecimal string, offering a high level of
|
||||||
|
uniqueness and security for identification purposes.
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
For additional information and examples, please consult the [Langflow Components Custom Documentation](http://docs.langflow.org/components/custom).
|
For additional information and examples, please consult the [Langflow Components Custom Documentation](http://docs.langflow.org/components/custom).
|
||||||
|
|
|
||||||
10
src/frontend/package-lock.json
generated
10
src/frontend/package-lock.json
generated
|
|
@ -60,6 +60,7 @@
|
||||||
"react-dom": "^18.2.21",
|
"react-dom": "^18.2.21",
|
||||||
"react-error-boundary": "^4.0.11",
|
"react-error-boundary": "^4.0.11",
|
||||||
"react-hook-form": "^7.51.4",
|
"react-hook-form": "^7.51.4",
|
||||||
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-laag": "^2.0.5",
|
"react-laag": "^2.0.5",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
|
|
@ -11022,6 +11023,15 @@
|
||||||
"react": "^16.8.0 || ^17 || ^18"
|
"react": "^16.8.0 || ^17 || ^18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hotkeys-hook": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.1",
|
||||||
|
"react-dom": ">=16.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@million/lint": "^0.0.73",
|
"@million/lint": "^0.0.73",
|
||||||
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import getFieldTitle from "../utils/get-field-title";
|
||||||
import sortFields from "../utils/sort-fields";
|
import sortFields from "../utils/sort-fields";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { postCustomComponent } from "../../controllers/API";
|
import { postCustomComponent } from "../../controllers/API";
|
||||||
import { cloneDeep } from "lodash";
|
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
@ -137,6 +137,8 @@ export default function GenericNode({
|
||||||
return names;
|
return names;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const [openWDoubleCLick, setOpenWDoubleCLick] = useState(false);
|
||||||
|
|
||||||
const getBaseBorderClass = (selected) => {
|
const getBaseBorderClass = (selected) => {
|
||||||
let className = selected ? "border border-ring" : "border";
|
let className = selected ? "border border-ring" : "border";
|
||||||
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
||||||
|
|
@ -231,10 +233,14 @@ export default function GenericNode({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
|
|
||||||
const memoizedNodeToolbarComponent = useMemo(() => {
|
const memoizedNodeToolbarComponent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<NodeToolbar>
|
<NodeToolbar>
|
||||||
<NodeToolbarComponent
|
<NodeToolbarComponent
|
||||||
|
// openWDoubleClick={openWDoubleCLick}
|
||||||
|
// setOpenWDoubleClick={setOpenWDoubleCLick}
|
||||||
data={data}
|
data={data}
|
||||||
deleteNode={(id) => {
|
deleteNode={(id) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
|
|
@ -266,11 +272,18 @@ export default function GenericNode({
|
||||||
updateNodeCode,
|
updateNodeCode,
|
||||||
isOutdated,
|
isOutdated,
|
||||||
selected,
|
selected,
|
||||||
|
shortcuts,
|
||||||
|
// openWDoubleCLick,
|
||||||
|
// setOpenWDoubleCLick,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{memoizedNodeToolbarComponent}
|
{memoizedNodeToolbarComponent}
|
||||||
<div
|
<div
|
||||||
|
// onDoubleClick={(event) => {
|
||||||
|
// if (!isWrappedWithClass(event, "nodoubleclick"))
|
||||||
|
// setOpenWDoubleCLick(true);
|
||||||
|
// }}
|
||||||
className={getNodeBorderClassName(
|
className={getNodeBorderClassName(
|
||||||
selected,
|
selected,
|
||||||
showNode,
|
showNode,
|
||||||
|
|
@ -344,7 +357,7 @@ export default function GenericNode({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
data-testid={"title-" + data.node?.display_name}
|
data-testid={"title-" + data.node?.display_name}
|
||||||
className="generic-node-tooltip-div cursor-text text-primary"
|
className="nodoubleclick generic-node-tooltip-div cursor-text text-primary"
|
||||||
>
|
>
|
||||||
{data.node?.display_name}
|
{data.node?.display_name}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -592,7 +605,7 @@ export default function GenericNode({
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
|
"nodoubleclick generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
|
||||||
(data.node?.description === "" ||
|
(data.node?.description === "" ||
|
||||||
!data.node?.description) &&
|
!data.node?.description) &&
|
||||||
nameEditable
|
nameEditable
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,53 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import IOModal from "../../modals/IOModal";
|
import IOModal from "../../modals/IOModal";
|
||||||
import ApiModal from "../../modals/apiModal/views";
|
import ApiModal from "../../modals/apiModal/views";
|
||||||
import ShareModal from "../../modals/shareModal";
|
import ShareModal from "../../modals/shareModal";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import { classNames } from "../../utils/utils";
|
import { classNames, isThereModal } from "../../utils/utils";
|
||||||
import ForwardedIconComponent from "../genericIconComponent";
|
import ForwardedIconComponent from "../genericIconComponent";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
export default function FlowToolbar(): JSX.Element {
|
export default function FlowToolbar(): JSX.Element {
|
||||||
const [open, setOpen] = useState(false);
|
const preventDefault = true;
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [openCodeModal, setOpenCodeModal] = useState<boolean>(false);
|
||||||
|
const [openShareModal, setOpenShareModal] = useState<boolean>(false);
|
||||||
|
function handleAPIWShortcut(e: KeyboardEvent) {
|
||||||
|
if (isThereModal() && !openCodeModal) return;
|
||||||
|
setOpenCodeModal((oldOpen) => !oldOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChatWShortcut(e: KeyboardEvent) {
|
||||||
|
if (isThereModal() && !open) return;
|
||||||
|
if (useFlowStore.getState().hasIO) {
|
||||||
|
setOpen((oldState) => !oldState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShareWShortcut(e: KeyboardEvent) {
|
||||||
|
if (isThereModal() && !openShareModal) return;
|
||||||
|
setOpenShareModal((oldState) => !oldState);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openPlayground = useShortcutsStore((state) => state.open);
|
||||||
|
const api = useShortcutsStore((state) => state.api);
|
||||||
|
const flow = useShortcutsStore((state) => state.flow);
|
||||||
|
|
||||||
|
useHotkeys(openPlayground, handleChatWShortcut, { preventDefault });
|
||||||
|
useHotkeys(api, handleAPIWShortcut, { preventDefault });
|
||||||
|
useHotkeys(flow, handleShareWShortcut, { preventDefault });
|
||||||
|
|
||||||
const hasIO = useFlowStore((state) => state.hasIO);
|
const hasIO = useFlowStore((state) => state.hasIO);
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
if (
|
|
||||||
(event.key === "K" || event.key === "k") &&
|
|
||||||
(event.metaKey || event.ctrlKey) &&
|
|
||||||
useFlowStore.getState().hasIO
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setOpen((oldState) => !oldState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const prevNodesRef = useRef<any[] | undefined>();
|
const prevNodesRef = useRef<any[] | undefined>();
|
||||||
|
|
||||||
const ModalMemo = useMemo(
|
const ModalMemo = useMemo(
|
||||||
|
|
@ -43,6 +56,8 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
is_component={false}
|
is_component={false}
|
||||||
component={currentFlow!}
|
component={currentFlow!}
|
||||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||||
|
open={openShareModal}
|
||||||
|
setOpen={setOpenShareModal}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||||
|
|
@ -66,7 +81,14 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
</button>
|
</button>
|
||||||
</ShareModal>
|
</ShareModal>
|
||||||
),
|
),
|
||||||
[hasApiKey, validApiKey, currentFlow, hasStore],
|
[
|
||||||
|
hasApiKey,
|
||||||
|
validApiKey,
|
||||||
|
currentFlow,
|
||||||
|
hasStore,
|
||||||
|
openShareModal,
|
||||||
|
setOpenShareModal,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -115,7 +137,11 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex cursor-pointer items-center gap-2">
|
<div className="flex cursor-pointer items-center gap-2">
|
||||||
{currentFlow && currentFlow.data && (
|
{currentFlow && currentFlow.data && (
|
||||||
<ApiModal flow={currentFlow}>
|
<ApiModal
|
||||||
|
flow={currentFlow}
|
||||||
|
open={openCodeModal}
|
||||||
|
setOpen={setOpenCodeModal}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
|
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
import { IS_MAC, SAVED_HOVER } from "../../../../constants/constants";
|
||||||
import ExportModal from "../../../../modals/exportModal";
|
import ExportModal from "../../../../modals/exportModal";
|
||||||
import FlowLogsModal from "../../../../modals/flowLogsModal";
|
import FlowLogsModal from "../../../../modals/flowLogsModal";
|
||||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||||
|
|
@ -20,8 +20,10 @@ import { cn } from "../../../../utils/utils";
|
||||||
import IconComponent from "../../../genericIconComponent";
|
import IconComponent from "../../../genericIconComponent";
|
||||||
import ShadTooltip from "../../../shadTooltipComponent";
|
import ShadTooltip from "../../../shadTooltipComponent";
|
||||||
import { Button } from "../../../ui/button";
|
import { Button } from "../../../ui/button";
|
||||||
|
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||||
|
|
||||||
export const MenuBar = ({}: {}): JSX.Element => {
|
export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
@ -113,7 +115,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
title: UPLOAD_ERROR_ALERT,
|
title: UPLOAD_ERROR_ALERT,
|
||||||
list: [error],
|
list: [error],
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -137,17 +139,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
>
|
>
|
||||||
<IconComponent name="Undo" className="header-menu-options " />
|
<IconComponent name="Undo" className="header-menu-options " />
|
||||||
Undo
|
Undo
|
||||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
{IS_MAC ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Command"
|
name="Command"
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||||
Ctrl +{" "}
|
{
|
||||||
|
shortcuts.find((s) => s.name.toLowerCase() === "undo")
|
||||||
|
?.shortcut
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="absolute right-2 top-[0.4em]">Z</span>
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -157,17 +161,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
>
|
>
|
||||||
<IconComponent name="Redo" className="header-menu-options " />
|
<IconComponent name="Redo" className="header-menu-options " />
|
||||||
Redo
|
Redo
|
||||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
{IS_MAC ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Command"
|
name="Command"
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||||
Ctrl +{" "}
|
{
|
||||||
|
shortcuts.find((s) => s.name.toLowerCase() === "redo")
|
||||||
|
?.shortcut
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="absolute right-2 top-[0.4em]">Y</span>
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
@ -195,7 +201,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4",
|
||||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
|
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{printByBuildStatus()}
|
{printByBuildStatus()}
|
||||||
|
|
|
||||||
46
src/frontend/src/components/renderIconComponent/index.tsx
Normal file
46
src/frontend/src/components/renderIconComponent/index.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import ForwardedIconComponent from "../genericIconComponent";
|
||||||
|
|
||||||
|
export default function RenderIcons({
|
||||||
|
isMac,
|
||||||
|
hasShift,
|
||||||
|
filteredShortcut,
|
||||||
|
shortcutWPlus,
|
||||||
|
}: {
|
||||||
|
isMac: boolean;
|
||||||
|
hasShift: boolean;
|
||||||
|
filteredShortcut: string[];
|
||||||
|
shortcutWPlus: string[];
|
||||||
|
}): JSX.Element {
|
||||||
|
return hasShift ? (
|
||||||
|
<>
|
||||||
|
{isMac ? (
|
||||||
|
<ForwardedIconComponent name="Command" className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
filteredShortcut[0]
|
||||||
|
)}
|
||||||
|
<ForwardedIconComponent name="ArrowBigUp" className="ml-1 h-5 w-5" />
|
||||||
|
{filteredShortcut.map((key, idx) => {
|
||||||
|
if (idx > 0) {
|
||||||
|
return <span className="ml-1"> {key.toUpperCase()} </span>;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{shortcutWPlus[0].toLowerCase() === "space" ? (
|
||||||
|
"Space"
|
||||||
|
) : shortcutWPlus[0].length <= 1 ? (
|
||||||
|
shortcutWPlus[0]
|
||||||
|
) : isMac ? (
|
||||||
|
<ForwardedIconComponent name="Command" className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
shortcutWPlus[0]
|
||||||
|
)}
|
||||||
|
{shortcutWPlus.map((key, idx) => {
|
||||||
|
if (idx > 0) {
|
||||||
|
return <span className="ml-0.5"> {key.toUpperCase()} </span>;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -33,10 +33,10 @@ const SelectContent = React.forwardRef<
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"relative z-50 min-w-[14rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -45,7 +45,7 @@ const SelectContent = React.forwardRef<
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -75,7 +75,7 @@ const SelectItem = React.forwardRef<
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -734,6 +734,91 @@ export const AUTHORIZED_DUPLICATE_REQUESTS = [
|
||||||
|
|
||||||
export const SAVE_DEBOUNCE_TIME = 300;
|
export const SAVE_DEBOUNCE_TIME = 300;
|
||||||
|
|
||||||
|
export const IS_MAC = navigator.userAgent.toUpperCase().includes("MAC");
|
||||||
|
|
||||||
|
export const defaultShortcuts = [
|
||||||
|
{
|
||||||
|
name: "Advanced Settings",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + A`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Minimize",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Q`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Code",
|
||||||
|
shortcut: `Space`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Copy",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + C`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duplicate",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + D`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Share",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + S`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Docs",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Save",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete",
|
||||||
|
shortcut: "Backspace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Open playground",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + K`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Undo",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Z`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Redo",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Y`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Group",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + G`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cut",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + X`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Paste",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + V`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + R`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Download",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + J`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + U`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Freeze",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + F`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Flow Share",
|
||||||
|
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + B`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to display right now. Please check back later.`;
|
export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to display right now. Please check back later.`;
|
||||||
|
|
||||||
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
||||||
|
|
@ -741,3 +826,6 @@ export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
||||||
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
||||||
|
|
||||||
export const MAX_BATCH_SIZE = 50;
|
export const MAX_BATCH_SIZE = 50;
|
||||||
|
|
||||||
|
export const MODAL_CLASSES =
|
||||||
|
"nopan nodelete nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@ import SvgGroqLogo from "./GroqLogo";
|
||||||
export const GroqIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
export const GroqIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
return <SvgGroqLogo ref={ref} {...props} />;
|
return <SvgGroqLogo ref={ref} {...props} />;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
252
src/frontend/src/modals/apiModal/index.tsx
Normal file
252
src/frontend/src/modals/apiModal/index.tsx
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
|
import "ace-builds/src-noconflict/mode-python";
|
||||||
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
import "ace-builds/src-noconflict/theme-twilight";
|
||||||
|
import {
|
||||||
|
ReactNode,
|
||||||
|
forwardRef,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
// import "ace-builds/webpack-resolver";
|
||||||
|
import CodeTabsComponent from "../../components/codeTabsComponent";
|
||||||
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
import {
|
||||||
|
EXPORT_CODE_DIALOG,
|
||||||
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
|
} from "../../constants/constants";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
import useFlowStore from "../../stores/flowStore";
|
||||||
|
import { TemplateVariableType } from "../../types/api";
|
||||||
|
import { tweakType, uniqueTweakType } from "../../types/components";
|
||||||
|
import { FlowType, NodeType } from "../../types/flow/index";
|
||||||
|
import { buildTweaks, convertArrayToObj } from "../../utils/reactflowUtils";
|
||||||
|
import {
|
||||||
|
getCurlCode,
|
||||||
|
getPythonApiCode,
|
||||||
|
getPythonCode,
|
||||||
|
getWidgetCode,
|
||||||
|
tabsArray,
|
||||||
|
} from "../../utils/utils";
|
||||||
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
const ApiModal = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
flow,
|
||||||
|
children,
|
||||||
|
open: myOpen,
|
||||||
|
setOpen: mySetOpen,
|
||||||
|
}: {
|
||||||
|
flow: FlowType;
|
||||||
|
children: ReactNode;
|
||||||
|
open: any;
|
||||||
|
setOpen: any;
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { autoLogin } = useContext(AuthContext);
|
||||||
|
const [open, setOpen] =
|
||||||
|
mySetOpen !== undefined && myOpen !== undefined
|
||||||
|
? [myOpen, mySetOpen]
|
||||||
|
: useState(false);
|
||||||
|
const [activeTab, setActiveTab] = useState("0");
|
||||||
|
const tweak = useRef<tweakType>([]);
|
||||||
|
const tweaksList = useRef<string[]>([]);
|
||||||
|
const [getTweak, setTweak] = useState<tweakType>([]);
|
||||||
|
const flowState = useFlowStore((state) => state.flowState);
|
||||||
|
const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current);
|
||||||
|
const curl_code = getCurlCode(flow, autoLogin, tweak.current);
|
||||||
|
const pythonCode = getPythonCode(flow, tweak.current);
|
||||||
|
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||||
|
const tweaksCode = buildTweaks(flow);
|
||||||
|
const codesArray = [
|
||||||
|
curl_code,
|
||||||
|
pythonApiCode,
|
||||||
|
pythonCode,
|
||||||
|
widgetCode,
|
||||||
|
pythonCode,
|
||||||
|
];
|
||||||
|
const [tabs, setTabs] = useState(tabsArray(codesArray, 0));
|
||||||
|
|
||||||
|
function startState() {
|
||||||
|
tweak.current = [];
|
||||||
|
setTweak([]);
|
||||||
|
tweaksList.current = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flow["data"]!["nodes"].length == 0) {
|
||||||
|
startState();
|
||||||
|
} else {
|
||||||
|
tweak.current = [];
|
||||||
|
const t = buildTweaks(flow);
|
||||||
|
tweak.current.push(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterNodes();
|
||||||
|
|
||||||
|
if (Object.keys(tweaksCode).length > 0) {
|
||||||
|
setActiveTab("0");
|
||||||
|
setTabs(tabsArray(codesArray, 1));
|
||||||
|
} else {
|
||||||
|
setTabs(tabsArray(codesArray, 1));
|
||||||
|
}
|
||||||
|
}, [flow["data"]!["nodes"], open]);
|
||||||
|
|
||||||
|
function filterNodes() {
|
||||||
|
let arrNodesWithValues: string[] = [];
|
||||||
|
|
||||||
|
flow["data"]!["nodes"].forEach((node) => {
|
||||||
|
if (!node["data"]["node"]["template"]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.keys(node["data"]["node"]["template"])
|
||||||
|
.filter(
|
||||||
|
(templateField) =>
|
||||||
|
templateField.charAt(0) !== "_" &&
|
||||||
|
node.data.node.template[templateField].show &&
|
||||||
|
LANGFLOW_SUPPORTED_TYPES.has(
|
||||||
|
node.data.node.template[templateField].type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((n, i) => {
|
||||||
|
arrNodesWithValues.push(node["id"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tweaksList.current = arrNodesWithValues.filter((value, index, self) => {
|
||||||
|
return self.indexOf(value) === index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function buildTweakObject(
|
||||||
|
tw: string,
|
||||||
|
changes: string | string[] | boolean | number | Object[] | Object,
|
||||||
|
template: TemplateVariableType
|
||||||
|
) {
|
||||||
|
if (typeof changes === "string" && template.type === "float") {
|
||||||
|
changes = parseFloat(changes);
|
||||||
|
}
|
||||||
|
if (typeof changes === "string" && template.type === "int") {
|
||||||
|
changes = parseInt(changes);
|
||||||
|
}
|
||||||
|
if (template.list === true && Array.isArray(changes)) {
|
||||||
|
changes = changes?.filter((x) => x !== "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.type === "dict" && Array.isArray(changes)) {
|
||||||
|
changes = convertArrayToObj(changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.type === "NestedDict") {
|
||||||
|
changes = JSON.stringify(changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingTweak = tweak.current.find((element) =>
|
||||||
|
element.hasOwnProperty(tw)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingTweak) {
|
||||||
|
existingTweak[tw][template["name"]!] = changes as string;
|
||||||
|
|
||||||
|
if (existingTweak[tw][template["name"]!] == template.value) {
|
||||||
|
tweak.current.forEach((element) => {
|
||||||
|
if (element[tw] && Object.keys(element[tw])?.length === 0) {
|
||||||
|
tweak.current = tweak.current.filter((obj) => {
|
||||||
|
const prop = obj[Object.keys(obj)[0]].prop;
|
||||||
|
return prop !== undefined && prop !== null && prop !== "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newTweak = {
|
||||||
|
[tw]: {
|
||||||
|
[template["name"]!]: changes,
|
||||||
|
},
|
||||||
|
} as uniqueTweakType;
|
||||||
|
tweak.current.push(newTweak);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current);
|
||||||
|
const curl_code = getCurlCode(flow, autoLogin, tweak.current);
|
||||||
|
const pythonCode = getPythonCode(flow, tweak.current);
|
||||||
|
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||||
|
|
||||||
|
tabs![0].code = curl_code;
|
||||||
|
tabs![1].code = pythonApiCode;
|
||||||
|
tabs![2].code = pythonCode;
|
||||||
|
tabs![3].code = widgetCode;
|
||||||
|
|
||||||
|
setTweak(tweak.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildContent(value: string) {
|
||||||
|
const htmlContent = (
|
||||||
|
<div className="w-[200px]">
|
||||||
|
<span>{value != null && value != "" ? value : "None"}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(
|
||||||
|
value: string,
|
||||||
|
node: NodeType,
|
||||||
|
template: TemplateVariableType
|
||||||
|
) {
|
||||||
|
let returnValue = value ?? "";
|
||||||
|
|
||||||
|
if (getTweak.length > 0) {
|
||||||
|
for (const obj of getTweak) {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const value = obj[key];
|
||||||
|
if (key == node["id"]) {
|
||||||
|
Object.keys(value).forEach((key) => {
|
||||||
|
if (key == template["name"]) {
|
||||||
|
returnValue = value[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value ?? "";
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseModal open={open} setOpen={setOpen}>
|
||||||
|
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||||
|
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
||||||
|
<span className="pr-2">API</span>
|
||||||
|
<IconComponent
|
||||||
|
name="Code2"
|
||||||
|
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</BaseModal.Header>
|
||||||
|
<BaseModal.Content>
|
||||||
|
<CodeTabsComponent
|
||||||
|
flow={flow}
|
||||||
|
tabs={tabs!}
|
||||||
|
activeTab={activeTab}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
|
tweaks={{
|
||||||
|
tweak,
|
||||||
|
tweaksList,
|
||||||
|
buildContent,
|
||||||
|
buildTweakObject,
|
||||||
|
getValue,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BaseModal.Content>
|
||||||
|
</BaseModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ApiModal;
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
export default function getPythonCode(
|
export default function getPythonCode(
|
||||||
flowName: string,
|
flowName: string,
|
||||||
tweaksBuildedObject
|
tweaksBuildedObject,
|
||||||
): string {
|
): string {
|
||||||
const tweaksObject = tweaksBuildedObject[0];
|
const tweaksObject = tweaksBuildedObject[0];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,13 @@ const ApiModal = forwardRef(
|
||||||
{
|
{
|
||||||
flow,
|
flow,
|
||||||
children,
|
children,
|
||||||
|
open: myOpen,
|
||||||
|
setOpen: mySetOpen,
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
open?: boolean;
|
||||||
|
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -44,7 +48,10 @@ const ApiModal = forwardRef(
|
||||||
|
|
||||||
const [activeTweaks, setActiveTweaks] = useState(false);
|
const [activeTweaks, setActiveTweaks] = useState(false);
|
||||||
const { autoLogin } = useContext(AuthContext);
|
const { autoLogin } = useContext(AuthContext);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] =
|
||||||
|
mySetOpen !== undefined && myOpen !== undefined
|
||||||
|
? [myOpen, mySetOpen]
|
||||||
|
: useState(false);
|
||||||
const [activeTab, setActiveTab] = useState("0");
|
const [activeTab, setActiveTab] = useState("0");
|
||||||
const pythonApiCode = getPythonApiCode(
|
const pythonApiCode = getPythonApiCode(
|
||||||
flow?.id,
|
flow?.id,
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ const EditNodeModal = forwardRef(
|
||||||
nodeLength,
|
nodeLength,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
|
// setOpenWDoubleClick,
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
nodeLength: number;
|
nodeLength: number;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
// setOpenWDoubleClick: (open: boolean) => void;
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
|
|
@ -55,6 +57,12 @@ const EditNodeModal = forwardRef(
|
||||||
}
|
}
|
||||||
}, [gridApi, open]);
|
}, [gridApi, open]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// return () => {
|
||||||
|
// setOpenWDoubleClick(false);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal
|
<BaseModal
|
||||||
key={data.id}
|
key={data.id}
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ export default function GenericModal({
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
!isEdit ? "rounded-lg border" : "",
|
!isEdit ? "rounded-lg border" : "",
|
||||||
"flex h-full w-full",
|
"flex h-full max-h-[85%] w-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{type === TypeModal.PROMPT && isEdit && !readonly ? (
|
{type === TypeModal.PROMPT && isEdit && !readonly ? (
|
||||||
|
|
@ -244,7 +244,7 @@ export default function GenericModal({
|
||||||
) : type !== TypeModal.PROMPT ? (
|
) : type !== TypeModal.PROMPT ? (
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={textRef}
|
ref={textRef}
|
||||||
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
|
className="form-input h-full w-full overflow-auto rounded-lg focus-visible:ring-1"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setInputValue(event.target.value);
|
setInputValue(event.target.value);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ export default function ShareModal({
|
||||||
|
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const [internalOpen, internalSetOpen] = useState(children ? false : true);
|
const [internalOpen, internalSetOpen] =
|
||||||
|
setOpen !== undefined && open !== undefined
|
||||||
|
? [open, setOpen]
|
||||||
|
: useState(children ? false : true);
|
||||||
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
|
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
|
||||||
const nameComponent = is_component ? "component" : "workflow";
|
const nameComponent = is_component ? "component" : "workflow";
|
||||||
|
|
||||||
|
|
@ -66,13 +69,13 @@ export default function ShareModal({
|
||||||
const description = component?.description ?? "";
|
const description = component?.description ?? "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open || internalOpen) {
|
if (internalOpen) {
|
||||||
if (hasApiKey && hasStore) {
|
if (hasApiKey && hasStore) {
|
||||||
handleGetTags();
|
handleGetTags();
|
||||||
handleGetNames();
|
handleGetNames();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [open, internalOpen, hasApiKey, hasStore]);
|
}, [internalOpen, hasApiKey, hasStore]);
|
||||||
|
|
||||||
function handleGetTags() {
|
function handleGetTags() {
|
||||||
setLoadingTags(true);
|
setLoadingTags(true);
|
||||||
|
|
@ -146,8 +149,7 @@ export default function ShareModal({
|
||||||
|
|
||||||
const handleUpdateComponent = () => {
|
const handleUpdateComponent = () => {
|
||||||
handleShareComponent(true);
|
handleShareComponent(true);
|
||||||
if (setOpen) setOpen(false);
|
internalSetOpen(false);
|
||||||
else internalSetOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExportComponent = () => {
|
const handleExportComponent = () => {
|
||||||
|
|
@ -199,8 +201,8 @@ export default function ShareModal({
|
||||||
<>
|
<>
|
||||||
<BaseModal
|
<BaseModal
|
||||||
size="smaller-h-full"
|
size="smaller-h-full"
|
||||||
open={(!disabled && open) ?? internalOpen}
|
open={!disabled && internalOpen}
|
||||||
setOpen={setOpen ?? internalSetOpen}
|
setOpen={internalSetOpen}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
const isNameAvailable = !unavaliableNames.some(
|
const isNameAvailable = !unavaliableNames.some(
|
||||||
(element) => element.name === name,
|
(element) => element.name === name,
|
||||||
|
|
@ -208,7 +210,7 @@ export default function ShareModal({
|
||||||
|
|
||||||
if (isNameAvailable) {
|
if (isNameAvailable) {
|
||||||
handleShareComponent();
|
handleShareComponent();
|
||||||
(setOpen || internalSetOpen)(false);
|
internalSetOpen(false);
|
||||||
} else {
|
} else {
|
||||||
setOpenConfirmationModal(true);
|
setOpenConfirmationModal(true);
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +291,7 @@ export default function ShareModal({
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
(setOpen || internalSetOpen)(false);
|
internalSetOpen(false);
|
||||||
handleExportComponent();
|
handleExportComponent();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
|
import {
|
||||||
|
KeyboardEvent,
|
||||||
|
MouseEvent,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Connection,
|
Connection,
|
||||||
|
|
@ -21,6 +29,7 @@ import {
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||||
import { useTypesStore } from "../../../../stores/typesStore";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { APIClassType } from "../../../../types/api";
|
import { APIClassType } from "../../../../types/api";
|
||||||
import { FlowType, NodeType } from "../../../../types/flow";
|
import { FlowType, NodeType } from "../../../../types/flow";
|
||||||
|
|
@ -50,6 +59,7 @@ export default function Page({
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
view?: boolean;
|
view?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
|
const preventDefault = true;
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
const autoSaveCurrentFlow = useFlowsManagerStore(
|
const autoSaveCurrentFlow = useFlowsManagerStore(
|
||||||
(state) => state.autoSaveCurrentFlow,
|
(state) => state.autoSaveCurrentFlow,
|
||||||
|
|
@ -141,119 +151,13 @@ export default function Page({
|
||||||
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
|
||||||
const selectedNode = lastSelection?.nodes ?? [];
|
|
||||||
const selectedEdges = lastSelection?.edges ?? [];
|
|
||||||
if (
|
|
||||||
selectionMenuVisible &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "g"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleGroupNode();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "p" &&
|
|
||||||
selectedNode.length > 0
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setNode(selectedNode[0].id, (old) => ({
|
|
||||||
...old,
|
|
||||||
data: {
|
|
||||||
...old.data,
|
|
||||||
node: {
|
|
||||||
...old.data.node,
|
|
||||||
frozen: old.data?.node?.frozen ? false : true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "d" &&
|
|
||||||
selectedNode.length > 0
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
paste(
|
|
||||||
{ nodes: selectedNode, edges: selectedEdges },
|
|
||||||
{
|
|
||||||
x: position.current.x,
|
|
||||||
y: position.current.y,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!isWrappedWithClass(event, "noundo")) {
|
|
||||||
if (
|
|
||||||
(event.key === "y" || (event.key === "z" && event.shiftKey)) &&
|
|
||||||
(event.ctrlKey || event.metaKey)
|
|
||||||
) {
|
|
||||||
event.preventDefault(); // prevent the default action
|
|
||||||
redo();
|
|
||||||
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
|
|
||||||
event.preventDefault();
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!isWrappedWithClass(event, "nocopy") &&
|
|
||||||
window.getSelection()?.toString().length === 0
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "c" &&
|
|
||||||
lastSelection
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
|
||||||
} else if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "x" &&
|
|
||||||
lastSelection
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setLastCopiedSelection(_.cloneDeep(lastSelection), true);
|
|
||||||
} else if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "v" &&
|
|
||||||
lastCopiedSelection
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
takeSnapshot();
|
|
||||||
paste(lastCopiedSelection, {
|
|
||||||
x: position.current.x,
|
|
||||||
y: position.current.y,
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key === "g" &&
|
|
||||||
lastSelection
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isWrappedWithClass(event, "nodelete")) {
|
|
||||||
if (
|
|
||||||
(event.key === "Delete" || event.key === "Backspace") &&
|
|
||||||
lastSelection
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
takeSnapshot();
|
|
||||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
|
||||||
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseMove = (event) => {
|
const handleMouseMove = (event) => {
|
||||||
position.current = { x: event.clientX, y: event.clientY };
|
position.current = { x: event.clientX, y: event.clientY };
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", onKeyDown);
|
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
};
|
};
|
||||||
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
|
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
|
||||||
|
|
@ -274,6 +178,115 @@ export default function Page({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
function handleUndo(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isWrappedWithClass(e, "noundo")) {
|
||||||
|
undo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRedo(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isWrappedWithClass(e, "noundo")) {
|
||||||
|
redo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGroup(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (selectionMenuVisible) {
|
||||||
|
handleGroupNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDuplicate(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const selectedNode = nodes.filter((obj) => obj.selected);
|
||||||
|
if (selectedNode.length > 0) {
|
||||||
|
paste(
|
||||||
|
{ nodes: selectedNode, edges: [] },
|
||||||
|
{
|
||||||
|
x: position.current.x,
|
||||||
|
y: position.current.y,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopy(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
lastSelection
|
||||||
|
) {
|
||||||
|
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
lastSelection
|
||||||
|
) {
|
||||||
|
setLastCopiedSelection(_.cloneDeep(lastSelection), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
lastCopiedSelection
|
||||||
|
) {
|
||||||
|
takeSnapshot();
|
||||||
|
paste(lastCopiedSelection, {
|
||||||
|
x: position.current.x,
|
||||||
|
y: position.current.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isWrappedWithClass(e, "nodelete") && lastSelection) {
|
||||||
|
takeSnapshot();
|
||||||
|
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||||
|
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const undoAction = useShortcutsStore((state) => state.undo);
|
||||||
|
const redoAction = useShortcutsStore((state) => state.redo);
|
||||||
|
const copyAction = useShortcutsStore((state) => state.copy);
|
||||||
|
const duplicate = useShortcutsStore((state) => state.duplicate);
|
||||||
|
const deleteAction = useShortcutsStore((state) => state.delete);
|
||||||
|
const groupAction = useShortcutsStore((state) => state.group);
|
||||||
|
const cutAction = useShortcutsStore((state) => state.cut);
|
||||||
|
const pasteAction = useShortcutsStore((state) => state.paste);
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(undoAction, handleUndo, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(redoAction, handleRedo, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(groupAction, handleGroup, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(duplicate, handleDuplicate, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(copyAction, handleCopy, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(cutAction, handleCut, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(pasteAction, handlePaste, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys(deleteAction, handleDelete, { preventDefault });
|
||||||
|
//@ts-ignore
|
||||||
|
useHotkeys("delete", handleDelete, { preventDefault });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSHowCanvas(
|
setSHowCanvas(
|
||||||
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
||||||
|
|
@ -473,6 +486,7 @@ export default function Page({
|
||||||
zoomOnScroll={!view}
|
zoomOnScroll={!view}
|
||||||
zoomOnPinch={!view}
|
zoomOnPinch={!view}
|
||||||
panOnDrag={!view}
|
panOnDrag={!view}
|
||||||
|
panActivationKeyCode={""}
|
||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
import { useUpdateNodeInternals } from "reactflow";
|
||||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent, {
|
||||||
|
ForwardedIconComponent,
|
||||||
|
} from "../../../../components/genericIconComponent";
|
||||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -10,7 +13,6 @@ import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "../../../../components/ui/select-custom";
|
} from "../../../../components/ui/select-custom";
|
||||||
import { postCustomComponent } from "../../../../controllers/API";
|
|
||||||
import ConfirmationModal from "../../../../modals/confirmationModal";
|
import ConfirmationModal from "../../../../modals/confirmationModal";
|
||||||
import EditNodeModal from "../../../../modals/editNodeModal";
|
import EditNodeModal from "../../../../modals/editNodeModal";
|
||||||
import ShareModal from "../../../../modals/shareModal";
|
import ShareModal from "../../../../modals/shareModal";
|
||||||
|
|
@ -18,6 +20,7 @@ import useAlertStore from "../../../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../../../stores/darkStore";
|
import { useDarkStore } from "../../../../stores/darkStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||||
import { useStoreStore } from "../../../../stores/storeStore";
|
import { useStoreStore } from "../../../../stores/storeStore";
|
||||||
import { useTypesStore } from "../../../../stores/typesStore";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { APIClassType } from "../../../../types/api";
|
import { APIClassType } from "../../../../types/api";
|
||||||
|
|
@ -29,8 +32,9 @@ import {
|
||||||
expandGroupNode,
|
expandGroupNode,
|
||||||
updateFlowPosition,
|
updateFlowPosition,
|
||||||
} from "../../../../utils/reactflowUtils";
|
} from "../../../../utils/reactflowUtils";
|
||||||
import { classNames, cn } from "../../../../utils/utils";
|
import { classNames, cn, isThereModal } from "../../../../utils/utils";
|
||||||
import ToolbarSelectItem from "./toolbarSelectItem";
|
import ToolbarSelectItem from "./toolbarSelectItem";
|
||||||
|
import RenderIcons from "../../../../components/renderIconComponent";
|
||||||
|
|
||||||
export default function NodeToolbarComponent({
|
export default function NodeToolbarComponent({
|
||||||
data,
|
data,
|
||||||
|
|
@ -43,6 +47,15 @@ export default function NodeToolbarComponent({
|
||||||
setShowState,
|
setShowState,
|
||||||
onCloseAdvancedModal,
|
onCloseAdvancedModal,
|
||||||
}: nodeToolbarPropsType): JSX.Element {
|
}: nodeToolbarPropsType): JSX.Element {
|
||||||
|
const version = useDarkStore((state) => state.version);
|
||||||
|
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||||
|
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||||
|
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
||||||
|
const [flowComponent, setFlowComponent] = useState<FlowType>(
|
||||||
|
createFlowComponent(cloneDeep(data), version),
|
||||||
|
);
|
||||||
|
const preventDefault = true;
|
||||||
|
const isMac = navigator.platform.toUpperCase().includes("MAC");
|
||||||
const nodeLength = Object.keys(data.node!.template).filter(
|
const nodeLength = Object.keys(data.node!.template).filter(
|
||||||
(templateField) =>
|
(templateField) =>
|
||||||
templateField.charAt(0) !== "_" &&
|
templateField.charAt(0) !== "_" &&
|
||||||
|
|
@ -58,10 +71,127 @@ export default function NodeToolbarComponent({
|
||||||
data.node.template[templateField].type === "dict" ||
|
data.node.template[templateField].type === "dict" ||
|
||||||
data.node.template[templateField].type === "NestedDict"),
|
data.node.template[templateField].type === "NestedDict"),
|
||||||
).length;
|
).length;
|
||||||
const templates = useTypesStore((state) => state.templates);
|
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
|
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||||
|
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isMinimal) {
|
||||||
|
setShowState((show) => !show);
|
||||||
|
setShowNode(data.showNode ?? true ? false : true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNoticeData({
|
||||||
|
title:
|
||||||
|
"Minimization are only available for nodes with one handle or fewer.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (hasApiKey || hasStore) {
|
||||||
|
handleSelectChange("update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGroupWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isGroup) {
|
||||||
|
handleSelectChange("ungroup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShareWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isThereModal() && !showOverrideModal) return;
|
||||||
|
if (hasApiKey || hasStore) {
|
||||||
|
setShowconfirmShare((state) => !state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCodeWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isThereModal() && !openModal) return;
|
||||||
|
if (hasCode) return setOpenModal((state) => !state);
|
||||||
|
setNoticeData({ title: `You can not access ${data.id} code` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdvancedWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isThereModal() && !showModalAdvanced) return;
|
||||||
|
if (!isGroup) {
|
||||||
|
setShowModalAdvanced((state) => !state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSaveWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isThereModal() && !showOverrideModal) return;
|
||||||
|
if (isSaved) {
|
||||||
|
setShowOverrideModal((state) => !state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasCode && !isSaved) {
|
||||||
|
saveComponent(cloneDeep(data), false);
|
||||||
|
setSuccessData({ title: `${data.id} saved successfully` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDocsWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (data.node?.documentation) {
|
||||||
|
return openInNewTab(data.node?.documentation);
|
||||||
|
}
|
||||||
|
setNoticeData({
|
||||||
|
title: `${data.id} docs is not available at the moment.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDownloadWShortcut(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
downloadNode(flowComponent!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFreeze(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setNode(data.id, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
frozen: old.data?.node?.frozen ? false : true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const advanced = useShortcutsStore((state) => state.advanced);
|
||||||
|
const minimize = useShortcutsStore((state) => state.minimize);
|
||||||
|
const share = useShortcutsStore((state) => state.share);
|
||||||
|
const save = useShortcutsStore((state) => state.save);
|
||||||
|
const docs = useShortcutsStore((state) => state.docs);
|
||||||
|
const code = useShortcutsStore((state) => state.code);
|
||||||
|
const group = useShortcutsStore((state) => state.group);
|
||||||
|
const update = useShortcutsStore((state) => state.update);
|
||||||
|
const download = useShortcutsStore((state) => state.download);
|
||||||
|
const freeze = useShortcutsStore((state) => state.freeze);
|
||||||
|
|
||||||
|
useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault });
|
||||||
|
useHotkeys(update, handleUpdateWShortcut, { preventDefault });
|
||||||
|
useHotkeys(group, handleGroupWShortcut, { preventDefault });
|
||||||
|
useHotkeys(share, handleShareWShortcut, { preventDefault });
|
||||||
|
useHotkeys(code, handleCodeWShortcut, { preventDefault });
|
||||||
|
useHotkeys(advanced, handleAdvancedWShortcut, { preventDefault });
|
||||||
|
useHotkeys(save, handleSaveWShortcut, { preventDefault });
|
||||||
|
useHotkeys(docs, handleDocsWShortcut, { preventDefault });
|
||||||
|
useHotkeys(download, handleDownloadWShortcut, { preventDefault });
|
||||||
|
useHotkeys(freeze, handleFreeze, { preventDefault });
|
||||||
|
|
||||||
const isMinimal = numberOfHandles <= 1;
|
const isMinimal = numberOfHandles <= 1;
|
||||||
const isGroup = data.node?.flow ? true : false;
|
const isGroup = data.node?.flow ? true : false;
|
||||||
|
|
@ -73,18 +203,14 @@ export default function NodeToolbarComponent({
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
|
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const setEdges = useFlowStore((state) => state.setEdges);
|
||||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
|
||||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
||||||
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
const version = useDarkStore((state) => state.version);
|
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
|
||||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
// useEffect(() => {
|
||||||
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
// if (openWDoubleClick) setShowModalAdvanced(true);
|
||||||
const [flowComponent, setFlowComponent] = useState<FlowType>(
|
// }, [openWDoubleClick, setOpenWDoubleClick]);
|
||||||
createFlowComponent(cloneDeep(data), version),
|
|
||||||
);
|
|
||||||
|
|
||||||
const openInNewTab = (url) => {
|
const openInNewTab = (url) => {
|
||||||
window.open(url, "_blank", "noreferrer");
|
window.open(url, "_blank", "noreferrer");
|
||||||
|
|
@ -118,6 +244,27 @@ export default function NodeToolbarComponent({
|
||||||
|
|
||||||
const handleSelectChange = (event) => {
|
const handleSelectChange = (event) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
case "save":
|
||||||
|
if (isSaved) {
|
||||||
|
return setShowOverrideModal(true);
|
||||||
|
}
|
||||||
|
saveComponent(cloneDeep(data), false);
|
||||||
|
break;
|
||||||
|
case "freeze":
|
||||||
|
setNode(data.id, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
frozen: old.data?.node?.frozen ? false : true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case "code":
|
||||||
|
setOpenModal(!openModal);
|
||||||
|
break;
|
||||||
case "advanced":
|
case "advanced":
|
||||||
setShowModalAdvanced(true);
|
setShowModalAdvanced(true);
|
||||||
break;
|
break;
|
||||||
|
|
@ -185,6 +332,42 @@ export default function NodeToolbarComponent({
|
||||||
Object.values(flow).includes(data.node?.display_name!),
|
Object.values(flow).includes(data.node?.display_name!),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function displayShortcut({
|
||||||
|
name,
|
||||||
|
shortcut,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
shortcut: string;
|
||||||
|
}): JSX.Element {
|
||||||
|
let hasShift: boolean = false;
|
||||||
|
const fixedShortcut = shortcut?.split("+");
|
||||||
|
fixedShortcut.forEach((key) => {
|
||||||
|
if (key.toLowerCase().includes("shift")) {
|
||||||
|
hasShift = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const filteredShortcut = fixedShortcut.filter(
|
||||||
|
(key) => !key.toLowerCase().includes("shift"),
|
||||||
|
);
|
||||||
|
let shortcutWPlus: string[] = [];
|
||||||
|
if (!hasShift) shortcutWPlus = filteredShortcut.join("+").split(" ");
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<span> {name} </span>
|
||||||
|
<span
|
||||||
|
className={`ml-3 flex items-center rounded-sm bg-muted px-1 py-[0.2] text-muted-foreground`}
|
||||||
|
>
|
||||||
|
<RenderIcons
|
||||||
|
isMac={isMac}
|
||||||
|
hasShift={hasShift}
|
||||||
|
filteredShortcut={filteredShortcut}
|
||||||
|
shortcutWPlus={shortcutWPlus}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
const handleOnNewValue = (
|
const handleOnNewValue = (
|
||||||
|
|
@ -235,131 +418,21 @@ export default function NodeToolbarComponent({
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const hasCode = Object.keys(data.node!.template).includes("code");
|
const hasCode = Object.keys(data.node!.template).includes("code");
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function onKeyDown(event: KeyboardEvent) {
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(hasApiKey || hasStore) &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key.toUpperCase() === "U"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleSelectChange("update");
|
|
||||||
}
|
|
||||||
if (selected && event.key.toUpperCase() === "ESCAPE") {
|
|
||||||
event.preventDefault();
|
|
||||||
handleSelectChange("unselect");
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
isGroup &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key.toUpperCase() === "G"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleSelectChange("ungroup");
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(hasApiKey || hasStore) &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey &&
|
|
||||||
event.key.toUpperCase() === "S"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setShowconfirmShare((state) => !state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey &&
|
|
||||||
event.key.toUpperCase() === "Q"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (isMinimal) {
|
|
||||||
setShowState((show) => !show);
|
|
||||||
setShowNode(data.showNode ?? true ? false : true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setNoticeData({
|
|
||||||
title:
|
|
||||||
"Minimization are only available for nodes with one handle or fewer.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey &&
|
|
||||||
event.key.toUpperCase() === "U"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (hasCode) return setOpenModal((state) => !state);
|
|
||||||
setNoticeData({ title: `You can not access ${data.id} code` });
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey &&
|
|
||||||
event.key.toUpperCase() === "A"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
setShowModalAdvanced((state) => !state);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key.toUpperCase() === "S"
|
|
||||||
) {
|
|
||||||
if (isSaved) {
|
|
||||||
event.preventDefault();
|
|
||||||
return setShowOverrideModal((state) => !state);
|
|
||||||
}
|
|
||||||
if (hasCode) {
|
|
||||||
event.preventDefault();
|
|
||||||
saveComponent(cloneDeep(data), false);
|
|
||||||
setSuccessData({ title: `${data.id} saved successfully` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey &&
|
|
||||||
event.key.toUpperCase() === "D"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (data.node?.documentation) {
|
|
||||||
return openInNewTab(data.node?.documentation);
|
|
||||||
}
|
|
||||||
setNoticeData({
|
|
||||||
title: `${data.id} docs is not available at the moment.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selected &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.key.toUpperCase() === "J"
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
downloadNode(flowComponent!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("keydown", onKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
|
||||||
};
|
|
||||||
}, [isSaved, showNode, data.showNode, isMinimal]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-26 nocopy nowheel nopan nodelete nodrag noundo h-10">
|
<div className="w-26 nocopy nowheel nopan nodelete nodrag noundo h-10">
|
||||||
<span className="isolate inline-flex rounded-md shadow-sm">
|
<span className="isolate inline-flex rounded-md shadow-sm">
|
||||||
{hasCode && (
|
{hasCode && (
|
||||||
<ShadTooltip content="Code" side="top">
|
<ShadTooltip
|
||||||
|
content={displayShortcut(
|
||||||
|
shortcuts.find(
|
||||||
|
({ name }) => name.split(" ")[0].toLowerCase() === "code",
|
||||||
|
)!,
|
||||||
|
)}
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="relative inline-flex items-center rounded-l-md bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
|
className="relative inline-flex items-center rounded-l-md bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenModal(!openModal);
|
setOpenModal(!openModal);
|
||||||
}}
|
}}
|
||||||
|
|
@ -369,8 +442,28 @@ export default function NodeToolbarComponent({
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
)}
|
)}
|
||||||
|
{nodeLength > 0 && (
|
||||||
|
<ShadTooltip
|
||||||
|
content={displayShortcut(
|
||||||
|
shortcuts.find(
|
||||||
|
({ name }) => name.split(" ")[0].toLowerCase() === "advanced",
|
||||||
|
)!,
|
||||||
|
)}
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={`${isGroup ? "rounded-l-md" : ""} relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowModalAdvanced(true);
|
||||||
|
}}
|
||||||
|
data-testid="code-button-modal"
|
||||||
|
>
|
||||||
|
<IconComponent name="Settings2" className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</ShadTooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
<ShadTooltip content={"Save"} side="top">
|
{/*<ShadTooltip content={"Save"} side="top">
|
||||||
<button
|
<button
|
||||||
data-testid="save-button-modal"
|
data-testid="save-button-modal"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
@ -387,24 +480,15 @@ export default function NodeToolbarComponent({
|
||||||
>
|
>
|
||||||
<IconComponent name="SaveAll" className="h-4 w-4" />
|
<IconComponent name="SaveAll" className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>*/}
|
||||||
|
<ShadTooltip
|
||||||
<ShadTooltip content={"Duplicate"} side="top">
|
content={displayShortcut(
|
||||||
<button
|
shortcuts.find(
|
||||||
data-testid="duplicate-button-modal"
|
({ name }) => name.split(" ")[0].toLowerCase() === "freeze",
|
||||||
className={classNames(
|
)!,
|
||||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
)}
|
||||||
)}
|
side="top"
|
||||||
onClick={(event) => {
|
>
|
||||||
event.preventDefault();
|
|
||||||
handleSelectChange("duplicate");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent name="Copy" className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</ShadTooltip>
|
|
||||||
|
|
||||||
<ShadTooltip content="Freeze" side="top">
|
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||||
|
|
@ -434,8 +518,23 @@ export default function NodeToolbarComponent({
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
|
|
||||||
|
{/*<ShadTooltip content={"Duplicate"} side="top">
|
||||||
|
<button
|
||||||
|
data-testid="duplicate-button-modal"
|
||||||
|
className={classNames(
|
||||||
|
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||||
|
)}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
handleSelectChange("duplicate");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent name="Copy" className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</ShadTooltip>*/}
|
||||||
|
|
||||||
<Select onValueChange={handleSelectChange} value="">
|
<Select onValueChange={handleSelectChange} value="">
|
||||||
<ShadTooltip content="More" side="top">
|
<ShadTooltip content="All" side="top">
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -453,23 +552,56 @@ export default function NodeToolbarComponent({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
{hasCode && (
|
||||||
|
<SelectItem value={"code"}>
|
||||||
|
<ToolbarSelectItem
|
||||||
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Code")?.shortcut!
|
||||||
|
}
|
||||||
|
value={"Code"}
|
||||||
|
icon={"Code"}
|
||||||
|
dataTestId="code-button-modal"
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{nodeLength > 0 && (
|
{nodeLength > 0 && (
|
||||||
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
keyboardKey="A"
|
shortcut={
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
shortcuts.find((obj) => obj.name === "Advanced Settings")
|
||||||
shift={true}
|
?.shortcut!
|
||||||
|
}
|
||||||
value={"Advanced"}
|
value={"Advanced"}
|
||||||
icon={"Settings2"}
|
icon={"Settings2"}
|
||||||
dataTestId="edit-button-modal"
|
dataTestId="edit-button-modal"
|
||||||
/>
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
<SelectItem value={"save"}>
|
||||||
|
<ToolbarSelectItem
|
||||||
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Save")?.shortcut!
|
||||||
|
}
|
||||||
|
value={"Save"}
|
||||||
|
icon={"SaveAll"}
|
||||||
|
dataTestId="save-button-modal"
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={"duplicate"}>
|
||||||
|
<ToolbarSelectItem
|
||||||
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut!
|
||||||
|
}
|
||||||
|
value={"Duplicate"}
|
||||||
|
icon={"Copy"}
|
||||||
|
dataTestId="copy-button-modal"
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
<SelectItem value={"copy"}>
|
<SelectItem value={"copy"}>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
keyboardKey="C"
|
shortcut={
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
shortcuts.find((obj) => obj.name === "Copy")?.shortcut!
|
||||||
shift={false}
|
}
|
||||||
value={"Copy"}
|
value={"Copy"}
|
||||||
icon={"Clipboard"}
|
icon={"Clipboard"}
|
||||||
dataTestId="copy-button-modal"
|
dataTestId="copy-button-modal"
|
||||||
|
|
@ -481,14 +613,11 @@ export default function NodeToolbarComponent({
|
||||||
disabled={!hasApiKey || !validApiKey}
|
disabled={!hasApiKey || !validApiKey}
|
||||||
>
|
>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
keyboardKey="S"
|
shortcut={
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
shortcuts.find((obj) => obj.name === "Share")?.shortcut!
|
||||||
shift={true}
|
}
|
||||||
value={"Share"}
|
value={"Share"}
|
||||||
icon={"Share3"}
|
icon={"Share3"}
|
||||||
styleObj={{
|
|
||||||
iconClasses: "relative top-0.5 -m-1 mr-[0.25rem] h-6 w-6",
|
|
||||||
}}
|
|
||||||
dataTestId="share-button-modal"
|
dataTestId="share-button-modal"
|
||||||
/>
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
@ -496,13 +625,13 @@ export default function NodeToolbarComponent({
|
||||||
{(!hasStore || !hasApiKey || !validApiKey) && (
|
{(!hasStore || !hasApiKey || !validApiKey) && (
|
||||||
<SelectItem value={"Download"}>
|
<SelectItem value={"Download"}>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
value="Download"
|
shortcut={
|
||||||
shift={false}
|
shortcuts.find((obj) => obj.name === "Download")
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
?.shortcut!
|
||||||
icon="Download"
|
}
|
||||||
styleObj={{ iconClasses: "relative top-0.5 mr-2 h-4 w-4" }}
|
value={"Download"}
|
||||||
keyboardKey={"J"}
|
icon={"Download"}
|
||||||
dataTestId={"Dowload-button-nodeToolbar"}
|
dataTestId="Download-button-modal"
|
||||||
/>
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|
@ -511,9 +640,9 @@ export default function NodeToolbarComponent({
|
||||||
disabled={data.node?.documentation === ""}
|
disabled={data.node?.documentation === ""}
|
||||||
>
|
>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
keyboardKey="D"
|
shortcut={
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
shortcuts.find((obj) => obj.name === "Docs")?.shortcut!
|
||||||
shift={true}
|
}
|
||||||
value={"Docs"}
|
value={"Docs"}
|
||||||
icon={"FileText"}
|
icon={"FileText"}
|
||||||
dataTestId="docs-button-modal"
|
dataTestId="docs-button-modal"
|
||||||
|
|
@ -522,38 +651,49 @@ export default function NodeToolbarComponent({
|
||||||
{isMinimal && (
|
{isMinimal && (
|
||||||
<SelectItem value={"show"}>
|
<SelectItem value={"show"}>
|
||||||
<ToolbarSelectItem
|
<ToolbarSelectItem
|
||||||
icon={showNode ? "Minimize2" : "Maximize2"}
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Minimize")
|
||||||
|
?.shortcut!
|
||||||
|
}
|
||||||
value={showNode ? "Minimize" : "Expand"}
|
value={showNode ? "Minimize" : "Expand"}
|
||||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
icon={showNode ? "Minimize2" : "Maximize2"}
|
||||||
shift={true}
|
dataTestId="minimize-button-modal"
|
||||||
keyboardKey={"Q"}
|
|
||||||
dataTestId={"minimize-button-nodeToolbar"}
|
|
||||||
/>
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
{isGroup && (
|
{isGroup && (
|
||||||
<SelectItem value="ungroup">
|
<SelectItem value="ungroup">
|
||||||
<div className="flex">
|
<ToolbarSelectItem
|
||||||
<IconComponent
|
shortcut={
|
||||||
name="Ungroup"
|
shortcuts.find((obj) => obj.name === "Group")?.shortcut!
|
||||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
}
|
||||||
/>{" "}
|
value={"Ungroup"}
|
||||||
<span className="">Ungroup</span>{" "}
|
icon={"Ungroup"}
|
||||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
dataTestId="group-button-modal"
|
||||||
<IconComponent
|
/>
|
||||||
name="Command"
|
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
|
||||||
></IconComponent>
|
|
||||||
) : (
|
|
||||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
|
|
||||||
Ctrl +{" "}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="absolute right-2 top-[0.43em]">G</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
<SelectItem value="freeze">
|
||||||
|
<ToolbarSelectItem
|
||||||
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Freeze")?.shortcut!
|
||||||
|
}
|
||||||
|
value={"Freeze"}
|
||||||
|
icon={"Snowflake"}
|
||||||
|
dataTestId="group-button-modal"
|
||||||
|
style={`${frozen ? " text-ice" : ""} transition-all`}
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Download">
|
||||||
|
<ToolbarSelectItem
|
||||||
|
shortcut={
|
||||||
|
shortcuts.find((obj) => obj.name === "Download")?.shortcut!
|
||||||
|
}
|
||||||
|
value={"Download"}
|
||||||
|
icon={"Download"}
|
||||||
|
dataTestId="download-button-modal"
|
||||||
|
/>
|
||||||
|
</SelectItem>
|
||||||
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
|
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
|
||||||
<div className="font-red flex text-status-red">
|
<div className="font-red flex text-status-red">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
@ -561,10 +701,10 @@ export default function NodeToolbarComponent({
|
||||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="">Delete</span>{" "}
|
<span className="">Delete</span>{" "}
|
||||||
<span>
|
<span className="justify absolute right-2 top-2 flex items-center rounded-sm bg-muted px-1 py-[0.2]">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Delete"
|
name="Delete"
|
||||||
className="absolute right-2 top-2 h-4 w-4 stroke-2 text-red-400"
|
className="h-4 w-4 stroke-2 text-red-400"
|
||||||
></IconComponent>
|
></IconComponent>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -597,12 +737,15 @@ export default function NodeToolbarComponent({
|
||||||
</span>
|
</span>
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
<EditNodeModal
|
{showModalAdvanced && (
|
||||||
data={data}
|
<EditNodeModal
|
||||||
nodeLength={nodeLength}
|
// setOpenWDoubleClick={setOpenWDoubleClick}
|
||||||
open={showModalAdvanced}
|
data={data}
|
||||||
setOpen={setShowModalAdvanced}
|
nodeLength={nodeLength}
|
||||||
/>
|
open={showModalAdvanced}
|
||||||
|
setOpen={setShowModalAdvanced}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showconfirmShare && (
|
{showconfirmShare && (
|
||||||
<ShareModal
|
<ShareModal
|
||||||
open={showconfirmShare}
|
open={showconfirmShare}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,49 @@
|
||||||
import ForwardedIconComponent from "../../../../../components/genericIconComponent";
|
import ForwardedIconComponent from "../../../../../components/genericIconComponent";
|
||||||
|
import RenderIcons from "../../../../../components/renderIconComponent";
|
||||||
import { toolbarSelectItemProps } from "../../../../../types/components";
|
import { toolbarSelectItemProps } from "../../../../../types/components";
|
||||||
|
|
||||||
export default function ToolbarSelectItem({
|
export default function ToolbarSelectItem({
|
||||||
shift,
|
|
||||||
isMac,
|
|
||||||
keyboardKey,
|
|
||||||
value,
|
value,
|
||||||
icon,
|
icon,
|
||||||
styleObj,
|
style,
|
||||||
dataTestId,
|
dataTestId,
|
||||||
ping,
|
ping,
|
||||||
|
shortcut,
|
||||||
}: toolbarSelectItemProps) {
|
}: toolbarSelectItemProps) {
|
||||||
|
const isMac = navigator.platform.toUpperCase().includes("MAC");
|
||||||
|
let hasShift = false;
|
||||||
|
const fixedShortcut = shortcut?.split("+");
|
||||||
|
fixedShortcut.forEach((key) => {
|
||||||
|
if (key.toLowerCase().includes("shift")) {
|
||||||
|
hasShift = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const filteredShortcut = fixedShortcut.filter(
|
||||||
|
(key) => !key.toLowerCase().includes("shift"),
|
||||||
|
);
|
||||||
|
let shortcutWPlus: string[] = [];
|
||||||
|
if (!hasShift) shortcutWPlus = filteredShortcut.join("+").split(" ");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex" data-testid={dataTestId}>
|
<div className={`flex ${style}`} data-testid={dataTestId}>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name={icon}
|
name={icon}
|
||||||
className={`relative top-0.5 mr-2 h-4 w-4 ${styleObj?.iconClasses} ${
|
className={` mr-2 ${icon === "Share3" ? "absolute left-2 top-[0.25em] h-6 w-6" : "mt-[0.15em] h-4 w-4"} ${
|
||||||
ping && "animate-pulse text-green-500"
|
ping && "animate-pulse text-green-500"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span className={styleObj?.valueClasses}>{value}</span>
|
<span className={`${icon === "Share3" ? "ml-[1.8em]" : " "}`}>
|
||||||
|
{value}
|
||||||
{isMac ? (
|
</span>
|
||||||
<ForwardedIconComponent
|
<span
|
||||||
name="Command"
|
className={`absolute right-2 top-[0.43em] flex items-center rounded-sm bg-muted px-1 py-[0.2] text-muted-foreground `}
|
||||||
className={`absolute
|
>
|
||||||
${shift ? " right-[2rem] " : "right-[1.15rem]"}
|
<RenderIcons
|
||||||
top-[0.65em] h-3.5 w-3.5 stroke-2 ${styleObj?.commandClasses}`}
|
isMac={isMac}
|
||||||
></ForwardedIconComponent>
|
hasShift={hasShift}
|
||||||
) : (
|
filteredShortcut={filteredShortcut}
|
||||||
<span
|
shortcutWPlus={shortcutWPlus}
|
||||||
className={`absolute ${
|
|
||||||
shift ? " right-[2.15rem] " : "right-[1.15rem]"
|
|
||||||
} top-[0.43em] stroke-2 `}
|
|
||||||
>
|
|
||||||
{shift ? "Ctrl" : "Ctrl +"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{shift && (
|
|
||||||
<ForwardedIconComponent
|
|
||||||
name="ArrowBigUp"
|
|
||||||
className={`absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2 ${styleObj?.shiftClasses}`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<span className={`absolute right-2 top-[0.43em] ${styleObj?.keyClasses}`}>
|
|
||||||
{keyboardKey}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ export async function addFolder(data: AddFolderType): Promise<FolderType> {
|
||||||
|
|
||||||
export async function updateFolder(
|
export async function updateFolder(
|
||||||
body: FolderType,
|
body: FolderType,
|
||||||
folderId: string
|
folderId: string,
|
||||||
): Promise<FolderType> {
|
): Promise<FolderType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.patch(
|
const response = await api.patch(
|
||||||
`${BASE_URL_API}folders/${folderId}`,
|
`${BASE_URL_API}folders/${folderId}`,
|
||||||
body
|
body,
|
||||||
);
|
);
|
||||||
return response?.data;
|
return response?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -68,7 +68,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`${BASE_URL_API}folders/download/${folderId}`
|
`${BASE_URL_API}folders/download/${folderId}`,
|
||||||
);
|
);
|
||||||
if (response?.status !== 200) {
|
if (response?.status !== 200) {
|
||||||
throw new Error(`HTTP error! status: ${response?.status}`);
|
throw new Error(`HTTP error! status: ${response?.status}`);
|
||||||
|
|
@ -82,7 +82,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadFlowsFromFolders(
|
export async function uploadFlowsFromFolders(
|
||||||
flows: FormData
|
flows: FormData,
|
||||||
): Promise<FlowType[]> {
|
): Promise<FlowType[]> {
|
||||||
try {
|
try {
|
||||||
const response = await api.post(`${BASE_URL_API}folders/upload/`, flows);
|
const response = await api.post(`${BASE_URL_API}folders/upload/`, flows);
|
||||||
|
|
@ -99,11 +99,11 @@ export async function uploadFlowsFromFolders(
|
||||||
|
|
||||||
export async function moveFlowToFolder(
|
export async function moveFlowToFolder(
|
||||||
flowId: string,
|
flowId: string,
|
||||||
folderId: string
|
folderId: string,
|
||||||
): Promise<FlowType> {
|
): Promise<FlowType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.patch(
|
const response = await api.patch(
|
||||||
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`
|
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`,
|
||||||
);
|
);
|
||||||
return response?.data;
|
return response?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import useAlertStore from "../../../../../stores/alertStore";
|
||||||
|
|
||||||
|
import ForwardedIconComponent from "../../../../../components/genericIconComponent";
|
||||||
|
import { Button } from "../../../../../components/ui/button";
|
||||||
|
import BaseModal from "../../../../../modals/baseModal";
|
||||||
|
import { useShortcutsStore } from "../../../../../stores/shortcuts";
|
||||||
|
import { toTitleCase } from "../../../../../utils/utils";
|
||||||
|
|
||||||
|
export default function EditShortcutButton({
|
||||||
|
children,
|
||||||
|
shortcut,
|
||||||
|
defaultShortcuts,
|
||||||
|
defaultCombination,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
disable,
|
||||||
|
setSelected,
|
||||||
|
}: {
|
||||||
|
children: JSX.Element;
|
||||||
|
shortcut: string[];
|
||||||
|
defaultShortcuts: Array<{ name: string; shortcut: string }>;
|
||||||
|
defaultCombination: string;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (bool: boolean) => void;
|
||||||
|
disable?: boolean;
|
||||||
|
setSelected: (selected: string[]) => void;
|
||||||
|
}): JSX.Element {
|
||||||
|
let shortcutInitialValue =
|
||||||
|
defaultShortcuts.length > 0
|
||||||
|
? defaultShortcuts.find(
|
||||||
|
(s) =>
|
||||||
|
s.name.split(" ")[0].toLowerCase().toLowerCase() ===
|
||||||
|
shortcut[0]?.split(" ")[0].toLowerCase(),
|
||||||
|
)?.shortcut
|
||||||
|
: "";
|
||||||
|
const [key, setKey] = useState<string | null>(null);
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
|
function canEditCombination(newCombination: string): boolean {
|
||||||
|
let canSave = true;
|
||||||
|
defaultShortcuts.forEach(({ shortcut }) => {
|
||||||
|
if (shortcut.toLowerCase() === newCombination.toLowerCase()) {
|
||||||
|
canSave = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return canSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setUniqueShortcut = useShortcutsStore(
|
||||||
|
(state) => state.updateUniqueShortcut,
|
||||||
|
);
|
||||||
|
|
||||||
|
function editCombination(): void {
|
||||||
|
if (key) {
|
||||||
|
if (canEditCombination(key)) {
|
||||||
|
const newCombination = defaultShortcuts.map((s) => {
|
||||||
|
if (s.name === shortcut[0]) {
|
||||||
|
return { name: s.name, shortcut: key };
|
||||||
|
}
|
||||||
|
return { name: s.name, shortcut: s.shortcut };
|
||||||
|
});
|
||||||
|
const fixCombination = key.split(" ");
|
||||||
|
if (
|
||||||
|
fixCombination[0].toLowerCase().includes("ctrl") ||
|
||||||
|
fixCombination[0].toLowerCase().includes("cmd")
|
||||||
|
) {
|
||||||
|
fixCombination[0] = "mod";
|
||||||
|
}
|
||||||
|
const shortcutName = shortcut[0].split(" ")[0].toLowerCase();
|
||||||
|
setUniqueShortcut(shortcutName, fixCombination.join("").toLowerCase());
|
||||||
|
setShortcuts(newCombination);
|
||||||
|
localStorage.setItem(
|
||||||
|
"langflow-shortcuts",
|
||||||
|
JSON.stringify(newCombination),
|
||||||
|
);
|
||||||
|
setKey(null);
|
||||||
|
setOpen(false);
|
||||||
|
setSuccessData({
|
||||||
|
title: `${shortcut[0]} shortcut successfully changed`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setErrorData({
|
||||||
|
title: "Error saving key combination",
|
||||||
|
list: ["This combination already exists!"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
setKey(null);
|
||||||
|
setSelected([]);
|
||||||
|
}
|
||||||
|
}, [open, setOpen, key]);
|
||||||
|
|
||||||
|
function getFixedCombination({
|
||||||
|
oldKey,
|
||||||
|
key,
|
||||||
|
}: {
|
||||||
|
oldKey: string;
|
||||||
|
key: string;
|
||||||
|
}): string {
|
||||||
|
if (oldKey === null) {
|
||||||
|
return `${key.length > 0 ? toTitleCase(key) : toTitleCase(key)}`;
|
||||||
|
}
|
||||||
|
return `${oldKey.length > 0 ? toTitleCase(oldKey) : oldKey.toUpperCase()} + ${key.length > 0 ? toTitleCase(key) : key.toUpperCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForKeys(keys: string, keyToCompare: string): boolean {
|
||||||
|
const keysArr = keys.split(" ");
|
||||||
|
let hasNewKey = false;
|
||||||
|
return keysArr.some(
|
||||||
|
(k) => k.toLowerCase().trim() === keyToCompare.toLowerCase().trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
let fixedKey = e.key;
|
||||||
|
if (e.key?.toLowerCase() === "control") {
|
||||||
|
fixedKey = "Ctrl";
|
||||||
|
}
|
||||||
|
if (e.key?.toLowerCase() === "meta") {
|
||||||
|
fixedKey = "Cmd";
|
||||||
|
}
|
||||||
|
if (e.key?.toLowerCase() === " ") {
|
||||||
|
fixedKey = "Space";
|
||||||
|
}
|
||||||
|
if (key) {
|
||||||
|
if (checkForKeys(key, fixedKey)) return;
|
||||||
|
}
|
||||||
|
setKey((oldKey) =>
|
||||||
|
getFixedCombination({ oldKey: oldKey!, key: fixedKey }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}, [key, setKey]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseModal open={open} setOpen={setOpen} size="x-small" disable={disable}>
|
||||||
|
<BaseModal.Header description={"Recording your keyboard"}>
|
||||||
|
<span className="pr-2"> Key Combination </span>
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Keyboard"
|
||||||
|
className="h-6 w-6 pl-1 text-primary "
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</BaseModal.Header>
|
||||||
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
|
<BaseModal.Content>
|
||||||
|
<div className="align-center flex h-full w-full justify-center gap-4 rounded-md border border-border py-2">
|
||||||
|
<div className="flex items-center justify-center text-center text-lg font-bold">
|
||||||
|
{key === null
|
||||||
|
? shortcutInitialValue?.toUpperCase()
|
||||||
|
: key.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseModal.Content>
|
||||||
|
<BaseModal.Footer>
|
||||||
|
<Button variant={"default"} onClick={editCombination}>
|
||||||
|
Apply
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="mr-5"
|
||||||
|
variant={"destructive"}
|
||||||
|
onClick={() => setKey(null)}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</BaseModal.Footer>
|
||||||
|
</BaseModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,16 @@
|
||||||
|
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
||||||
import TableComponent from "../../../../components/tableComponent";
|
import TableComponent from "../../../../components/tableComponent";
|
||||||
|
import { Button } from "../../../../components/ui/button";
|
||||||
|
import { defaultShortcuts } from "../../../../constants/constants";
|
||||||
|
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||||
|
import EditShortcutButton from "./EditShortcutButton";
|
||||||
|
|
||||||
export default function ShortcutsPage() {
|
export default function ShortcutsPage() {
|
||||||
const isMac = navigator.userAgent.toUpperCase().includes("MAC");
|
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||||
const advancedShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + A`;
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const minizmizeShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + Q`;
|
const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
|
||||||
const codeShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + C`;
|
|
||||||
const copyShortcut = `${isMac ? "Cmd" : "Ctrl"} + C`;
|
|
||||||
const duplicateShortcut = `${isMac ? "Cmd" : "Ctrl"} + D`;
|
|
||||||
const shareShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + S`;
|
|
||||||
const docsShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + D`;
|
|
||||||
const saveShortcut = `${isMac ? "Cmd" : "Ctrl"} + S`;
|
|
||||||
const deleteShortcut = `Backspace`;
|
|
||||||
const interactionShortcut = `${isMac ? "Cmd" : "Ctrl"} + K`;
|
|
||||||
const undoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Z`;
|
|
||||||
const redoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Y`;
|
|
||||||
|
|
||||||
// Column Definitions: Defines the columns to be displayed.
|
// Column Definitions: Defines the columns to be displayed.
|
||||||
const colDefs = [
|
const colDefs = [
|
||||||
|
|
@ -23,8 +19,10 @@ export default function ShortcutsPage() {
|
||||||
field: "name",
|
field: "name",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
editable: false,
|
editable: false,
|
||||||
|
resizable: false,
|
||||||
}, //This column will be twice as wide as the others
|
}, //This column will be twice as wide as the others
|
||||||
{
|
{
|
||||||
|
headerName: "Keyboard Shortcut",
|
||||||
field: "shortcut",
|
field: "shortcut",
|
||||||
flex: 2,
|
flex: 2,
|
||||||
editable: false,
|
editable: false,
|
||||||
|
|
@ -32,71 +30,31 @@ export default function ShortcutsPage() {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const nodesRowData = [
|
const [nodesRowData, setNodesRowData] = useState<
|
||||||
{
|
Array<{ name: string; shortcut: string }>
|
||||||
name: "Advanced Settings Component",
|
>([]);
|
||||||
shortcut: advancedShortcut,
|
|
||||||
resizable: false,
|
useEffect(() => {
|
||||||
},
|
setNodesRowData(shortcuts);
|
||||||
{
|
}, [shortcuts]);
|
||||||
name: "Minimize Component",
|
|
||||||
shortcut: minizmizeShortcut,
|
const combinationToEdit = shortcuts.filter((s) => s.name === selectedRows[0]);
|
||||||
resizable: false,
|
const [open, setOpen] = useState(false);
|
||||||
},
|
const updateUniqueShortcut = useShortcutsStore(
|
||||||
{
|
(state) => state.updateUniqueShortcut,
|
||||||
name: "Code Component",
|
);
|
||||||
shortcut: codeShortcut,
|
|
||||||
resizable: false,
|
function handleRestore() {
|
||||||
},
|
setShortcuts(defaultShortcuts);
|
||||||
{
|
defaultShortcuts.forEach(({ name, shortcut }) => {
|
||||||
name: "Copy Component",
|
const fixedName = name.split(" ")[0].toLowerCase();
|
||||||
shortcut: copyShortcut,
|
updateUniqueShortcut(fixedName, shortcut);
|
||||||
resizable: false,
|
});
|
||||||
},
|
localStorage.removeItem("langflow-shortcuts");
|
||||||
{
|
}
|
||||||
name: "Duplicate Component",
|
|
||||||
shortcut: duplicateShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Share Component",
|
|
||||||
shortcut: shareShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Docs Component",
|
|
||||||
shortcut: docsShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Save Component",
|
|
||||||
shortcut: saveShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Delete Component",
|
|
||||||
shortcut: deleteShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Open Playground",
|
|
||||||
shortcut: interactionShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Undo",
|
|
||||||
shortcut: undoShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Redo",
|
|
||||||
shortcut: redoShortcut,
|
|
||||||
resizable: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col gap-6">
|
<div className="flex h-full w-full flex-col gap-6 ">
|
||||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||||
|
|
@ -110,14 +68,48 @@ export default function ShortcutsPage() {
|
||||||
Manage Shortcuts for quick access to frequently used actions.
|
Manage Shortcuts for quick access to frequently used actions.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="align-end mb-4 flex w-full justify-end">
|
||||||
|
<div className="justify center flex items-center">
|
||||||
|
{open && (
|
||||||
|
<EditShortcutButton
|
||||||
|
disable={selectedRows.length === 0}
|
||||||
|
defaultCombination={combinationToEdit[0]?.shortcut}
|
||||||
|
shortcut={selectedRows}
|
||||||
|
defaultShortcuts={shortcuts}
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
setSelected={setSelectedRows}
|
||||||
|
>
|
||||||
|
<div style={{ display: "none" }} />
|
||||||
|
</EditShortcutButton>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
className="ml-3"
|
||||||
|
onClick={handleRestore}
|
||||||
|
>
|
||||||
|
<ForwardedIconComponent name="RotateCcw" className="mr-2 w-4" />
|
||||||
|
Restore
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full w-full flex-col justify-between">
|
<div className="grid gap-6 pb-8">
|
||||||
<TableComponent
|
<div>
|
||||||
key={"shortcuts"}
|
<TableComponent
|
||||||
pagination={false}
|
suppressRowClickSelection={true}
|
||||||
columnDefs={colDefs}
|
domLayout="autoHeight"
|
||||||
rowData={nodesRowData}
|
pagination={false}
|
||||||
/>
|
columnDefs={colDefs}
|
||||||
|
rowData={nodesRowData}
|
||||||
|
onCellDoubleClicked={(e) => {
|
||||||
|
setSelectedRows([e.data.name]);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,50 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import { defaultShortcuts } from "../constants/constants";
|
||||||
import { shortcutsStoreType } from "../types/store";
|
import { shortcutsStoreType } from "../types/store";
|
||||||
|
|
||||||
export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
|
export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
|
||||||
openCodeModalWShortcut: false,
|
shortcuts: defaultShortcuts,
|
||||||
handleModalWShortcut: (modal) => {
|
setShortcuts: (newShortcuts) => {
|
||||||
switch (modal) {
|
set({ shortcuts: newShortcuts });
|
||||||
case "code":
|
},
|
||||||
|
flow: "mod+b",
|
||||||
|
undo: "mod+z",
|
||||||
|
redo: "mod+y",
|
||||||
|
open: "mod+k",
|
||||||
|
advanced: "mod+shift+a",
|
||||||
|
minimize: "mod+shift+q",
|
||||||
|
code: "space",
|
||||||
|
copy: "mod+c",
|
||||||
|
duplicate: "mod+d",
|
||||||
|
share: "mod+shift+s",
|
||||||
|
docs: "mod+shift+d",
|
||||||
|
save: "mod+s",
|
||||||
|
delete: "backspace",
|
||||||
|
group: "mod+g",
|
||||||
|
cut: "mod+x",
|
||||||
|
paste: "mod+v",
|
||||||
|
api: "mod+r",
|
||||||
|
update: "mod+u",
|
||||||
|
download: "mod+j",
|
||||||
|
freeze: "mod+f",
|
||||||
|
updateUniqueShortcut: (name, combination) => {
|
||||||
|
set({
|
||||||
|
[name]: combination,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getShortcutsFromStorage: () => {
|
||||||
|
if (localStorage.getItem("langflow-shortcuts")) {
|
||||||
|
const savedShortcuts = localStorage.getItem("langflow-shortcuts");
|
||||||
|
const savedArr = JSON.parse(savedShortcuts!);
|
||||||
|
savedArr.forEach(({ name, shortcut }) => {
|
||||||
|
let shortcutName = name.split(" ")[0].toLowerCase();
|
||||||
set({
|
set({
|
||||||
openCodeModalWShortcut: !get().openCodeModalWShortcut,
|
[shortcutName]: shortcut,
|
||||||
});
|
});
|
||||||
break;
|
});
|
||||||
|
get().setShortcuts(JSON.parse(savedShortcuts!));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
useShortcutsStore.getState().getShortcutsFromStorage();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
font-feature-settings: "rlig" 1, "calt" 1;
|
font-feature-settings:
|
||||||
|
"rlig" 1,
|
||||||
|
"calt" 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ pre {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.react-flow__pane {
|
.react-flow__pane {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
--accent: 210 30% 96.1%; /* hsl(210 30% 96%) */
|
--accent: 210 30% 96.1%; /* hsl(210 30% 96%) */
|
||||||
--accent-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--accent-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */
|
--destructive: 0 60% 40%; /* hsl(0 60% 40%) */
|
||||||
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
|
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
|
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
|
||||||
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
|
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
||||||
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
|
--destructive: 0 60% 25%; /* hsl(0 60% 25%) */
|
||||||
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
||||||
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
|
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
|
||||||
|
|
|
||||||
|
|
@ -491,7 +491,7 @@ export type ChatInputType = {
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
files: FilePreviewType[];
|
files: FilePreviewType[];
|
||||||
setFiles: (
|
setFiles: (
|
||||||
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[]),
|
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[])
|
||||||
) => void;
|
) => void;
|
||||||
chatValue: string;
|
chatValue: string;
|
||||||
inputRef: {
|
inputRef: {
|
||||||
|
|
@ -535,6 +535,8 @@ export type fileCardPropsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type nodeToolbarPropsType = {
|
export type nodeToolbarPropsType = {
|
||||||
|
// openWDoubleClick: boolean;
|
||||||
|
// setOpenWDoubleClick: (open: boolean) => void;
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
deleteNode: (idx: string) => void;
|
deleteNode: (idx: string) => void;
|
||||||
setShowNode: (boolean: any) => void;
|
setShowNode: (boolean: any) => void;
|
||||||
|
|
@ -591,7 +593,7 @@ export type chatMessagePropsType = {
|
||||||
updateChat: (
|
updateChat: (
|
||||||
chat: ChatMessageType,
|
chat: ChatMessageType,
|
||||||
message: string,
|
message: string,
|
||||||
stream_url?: string,
|
stream_url?: string
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -683,12 +685,12 @@ export type codeTabsPropsType = {
|
||||||
value: string,
|
value: string,
|
||||||
node: NodeType,
|
node: NodeType,
|
||||||
template: TemplateVariableType,
|
template: TemplateVariableType,
|
||||||
tweak: tweakType,
|
tweak: tweakType
|
||||||
) => string;
|
) => string;
|
||||||
buildTweakObject?: (
|
buildTweakObject?: (
|
||||||
tw: string,
|
tw: string,
|
||||||
changes: string | string[] | boolean | number | Object[] | Object,
|
changes: string | string[] | boolean | number | Object[] | Object,
|
||||||
template: TemplateVariableType,
|
template: TemplateVariableType
|
||||||
) => Promise<string | void>;
|
) => Promise<string | void>;
|
||||||
};
|
};
|
||||||
activeTweaks?: boolean;
|
activeTweaks?: boolean;
|
||||||
|
|
@ -770,21 +772,12 @@ export type IOFileInputProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type toolbarSelectItemProps = {
|
export type toolbarSelectItemProps = {
|
||||||
isMac: boolean;
|
|
||||||
shift: boolean;
|
|
||||||
keyboardKey: string;
|
|
||||||
value: string;
|
value: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
styleObj?: {
|
style?: string;
|
||||||
iconClasses?: string;
|
|
||||||
commandClasses?: string;
|
|
||||||
shiftClasses?: string;
|
|
||||||
ctrlClasses?: string;
|
|
||||||
keyClasses?: string;
|
|
||||||
valueClasses?: string;
|
|
||||||
};
|
|
||||||
dataTestId: string;
|
dataTestId: string;
|
||||||
ping?: boolean;
|
ping?: boolean;
|
||||||
|
shortcut: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type clearChatPropsType = {
|
export type clearChatPropsType = {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,33 @@ export type StoreComponentResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type shortcutsStoreType = {
|
export type shortcutsStoreType = {
|
||||||
openCodeModalWShortcut: boolean;
|
updateUniqueShortcut: (name: string, combination: string) => void;
|
||||||
handleModalWShortcut: (str: string) => void;
|
flow: string;
|
||||||
|
group: string;
|
||||||
|
cut: string;
|
||||||
|
paste: string;
|
||||||
|
api: string;
|
||||||
|
open: string;
|
||||||
|
undo: string;
|
||||||
|
redo: string;
|
||||||
|
advanced: string;
|
||||||
|
minimize: string;
|
||||||
|
code: string;
|
||||||
|
copy: string;
|
||||||
|
duplicate: string;
|
||||||
|
share: string;
|
||||||
|
docs: string;
|
||||||
|
save: string;
|
||||||
|
delete: string;
|
||||||
|
update: string;
|
||||||
|
download: string;
|
||||||
|
freeze: string;
|
||||||
|
shortcuts: Array<{
|
||||||
|
name: string;
|
||||||
|
shortcut: string;
|
||||||
|
}>;
|
||||||
|
setShortcuts: (
|
||||||
|
newShortcuts: Array<{ name: string; shortcut: string }>,
|
||||||
|
) => void;
|
||||||
|
getShortcutsFromStorage: () => void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import ShortUniqueId from "short-unique-id";
|
||||||
import getFieldTitle from "../CustomNodes/utils/get-field-title";
|
import getFieldTitle from "../CustomNodes/utils/get-field-title";
|
||||||
import {
|
import {
|
||||||
INPUT_TYPES,
|
INPUT_TYPES,
|
||||||
|
IS_MAC,
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
OUTPUT_TYPES,
|
OUTPUT_TYPES,
|
||||||
SUCCESS_BUILD,
|
SUCCESS_BUILD,
|
||||||
|
|
@ -99,18 +100,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
||||||
export function isValidConnection(
|
export function isValidConnection(
|
||||||
{ source, target, sourceHandle, targetHandle }: Connection,
|
{ source, target, sourceHandle, targetHandle }: Connection,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
edges: Edge[],
|
edges: Edge[]
|
||||||
) {
|
) {
|
||||||
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
||||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
||||||
if (
|
if (
|
||||||
targetHandleObject.inputTypes?.some(
|
targetHandleObject.inputTypes?.some(
|
||||||
(n) => n === sourceHandleObject.dataType,
|
(n) => n === sourceHandleObject.dataType
|
||||||
) ||
|
) ||
|
||||||
sourceHandleObject.baseClasses.some(
|
sourceHandleObject.baseClasses.some(
|
||||||
(t) =>
|
(t) =>
|
||||||
targetHandleObject.inputTypes?.some((n) => n === t) ||
|
targetHandleObject.inputTypes?.some((n) => n === t) ||
|
||||||
t === targetHandleObject.type,
|
t === targetHandleObject.type
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
|
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
|
||||||
|
|
@ -143,7 +144,7 @@ export function removeApiKeys(flow: FlowType): FlowType {
|
||||||
|
|
||||||
export function updateTemplate(
|
export function updateTemplate(
|
||||||
reference: APITemplateType,
|
reference: APITemplateType,
|
||||||
objectToUpdate: APITemplateType,
|
objectToUpdate: APITemplateType
|
||||||
): APITemplateType {
|
): APITemplateType {
|
||||||
let clonedObject: APITemplateType = cloneDeep(reference);
|
let clonedObject: APITemplateType = cloneDeep(reference);
|
||||||
|
|
||||||
|
|
@ -203,7 +204,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
|
||||||
|
|
||||||
export function updateIds(
|
export function updateIds(
|
||||||
{ edges, nodes }: { edges: Edge[]; nodes: Node[] },
|
{ edges, nodes }: { edges: Edge[]; nodes: Node[] },
|
||||||
selection?: { edges: Edge[]; nodes: Node[] },
|
selection?: { edges: Edge[]; nodes: Node[] }
|
||||||
) {
|
) {
|
||||||
let idsMap = {};
|
let idsMap = {};
|
||||||
const selectionIds = selection?.nodes.map((n) => n.id);
|
const selectionIds = selection?.nodes.map((n) => n.id);
|
||||||
|
|
@ -231,7 +232,7 @@ export function updateIds(
|
||||||
edge.source = idsMap[edge.source];
|
edge.source = idsMap[edge.source];
|
||||||
edge.target = idsMap[edge.target];
|
edge.target = idsMap[edge.target];
|
||||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||||
edge.sourceHandle!,
|
edge.sourceHandle!
|
||||||
);
|
);
|
||||||
edge.sourceHandle = scapedJSONStringfy({
|
edge.sourceHandle = scapedJSONStringfy({
|
||||||
...sourceHandleObject,
|
...sourceHandleObject,
|
||||||
|
|
@ -241,7 +242,7 @@ export function updateIds(
|
||||||
edge.data.sourceHandle.id = edge.source;
|
edge.data.sourceHandle.id = edge.source;
|
||||||
}
|
}
|
||||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||||
edge.targetHandle!,
|
edge.targetHandle!
|
||||||
);
|
);
|
||||||
edge.targetHandle = scapedJSONStringfy({
|
edge.targetHandle = scapedJSONStringfy({
|
||||||
...targetHandleObject,
|
...targetHandleObject,
|
||||||
|
|
@ -287,11 +288,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
||||||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
|
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
|
||||||
t &&
|
t &&
|
||||||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
|
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
|
||||||
node.id,
|
node.id
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${displayName || type} is missing ${getFieldTitle(template, t)}.`,
|
`${displayName || type} is missing ${getFieldTitle(template, t)}.`
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
template[t].type === "dict" &&
|
template[t].type === "dict" &&
|
||||||
|
|
@ -305,15 +306,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${displayName || type} (${getFieldTitle(
|
`${displayName || type} (${getFieldTitle(
|
||||||
template,
|
template,
|
||||||
t,
|
t
|
||||||
)}) contains duplicate keys with the same values.`,
|
)}) contains duplicate keys with the same values.`
|
||||||
);
|
);
|
||||||
if (hasEmptyKey(template[t].value))
|
if (hasEmptyKey(template[t].value))
|
||||||
errors.push(
|
errors.push(
|
||||||
`${displayName || type} (${getFieldTitle(
|
`${displayName || type} (${getFieldTitle(
|
||||||
template,
|
template,
|
||||||
t,
|
t
|
||||||
)}) field must not be empty.`,
|
)}) field must not be empty.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
|
|
@ -322,7 +323,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
||||||
|
|
||||||
export function validateNodes(
|
export function validateNodes(
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
edges: Edge[],
|
edges: Edge[]
|
||||||
): // this returns an array of tuples with the node id and the errors
|
): // this returns an array of tuples with the node id and the errors
|
||||||
Array<{ id: string; errors: Array<string> }> {
|
Array<{ id: string; errors: Array<string> }> {
|
||||||
if (nodes.length === 0) {
|
if (nodes.length === 0) {
|
||||||
|
|
@ -343,7 +344,7 @@ export function updateEdges(edges: Edge[]) {
|
||||||
if (edges)
|
if (edges)
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||||
edge.targetHandle!,
|
edge.targetHandle!
|
||||||
);
|
);
|
||||||
edge.className = "";
|
edge.className = "";
|
||||||
});
|
});
|
||||||
|
|
@ -410,7 +411,7 @@ export function handleKeyDown(
|
||||||
| React.KeyboardEvent<HTMLInputElement>
|
| React.KeyboardEvent<HTMLInputElement>
|
||||||
| React.KeyboardEvent<HTMLTextAreaElement>,
|
| React.KeyboardEvent<HTMLTextAreaElement>,
|
||||||
inputValue: string | string[] | null,
|
inputValue: string | string[] | null,
|
||||||
block: string,
|
block: string
|
||||||
) {
|
) {
|
||||||
//condition to fix bug control+backspace on Windows/Linux
|
//condition to fix bug control+backspace on Windows/Linux
|
||||||
if (
|
if (
|
||||||
|
|
@ -420,9 +421,7 @@ export function handleKeyDown(
|
||||||
(inputValue === block ||
|
(inputValue === block ||
|
||||||
inputValue?.charAt(inputValue?.length - 1) === " " ||
|
inputValue?.charAt(inputValue?.length - 1) === " " ||
|
||||||
specialCharsRegex.test(inputValue?.charAt(inputValue?.length - 1)))) ||
|
specialCharsRegex.test(inputValue?.charAt(inputValue?.length - 1)))) ||
|
||||||
(navigator.userAgent.toUpperCase().includes("MAC") &&
|
(IS_MAC && e.ctrlKey === true && e.key === "Backspace")
|
||||||
e.ctrlKey === true &&
|
|
||||||
e.key === "Backspace")
|
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -435,7 +434,7 @@ export function handleKeyDown(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleOnlyIntegerInput(
|
export function handleOnlyIntegerInput(
|
||||||
event: React.KeyboardEvent<HTMLInputElement>,
|
event: React.KeyboardEvent<HTMLInputElement>
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
event.key === "." ||
|
event.key === "." ||
|
||||||
|
|
@ -451,7 +450,7 @@ export function handleOnlyIntegerInput(
|
||||||
|
|
||||||
export function getConnectedNodes(
|
export function getConnectedNodes(
|
||||||
edge: Edge,
|
edge: Edge,
|
||||||
nodes: Array<NodeType>,
|
nodes: Array<NodeType>
|
||||||
): Array<NodeType> {
|
): Array<NodeType> {
|
||||||
const sourceId = edge.source;
|
const sourceId = edge.source;
|
||||||
const targetId = edge.target;
|
const targetId = edge.target;
|
||||||
|
|
@ -552,7 +551,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
|
||||||
!edge.sourceHandle ||
|
!edge.sourceHandle ||
|
||||||
!edge.targetHandle ||
|
!edge.targetHandle ||
|
||||||
!edge.sourceHandle.includes("{") ||
|
!edge.sourceHandle.includes("{") ||
|
||||||
!edge.targetHandle.includes("{"),
|
!edge.targetHandle.includes("{")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -575,7 +574,7 @@ export function customStringify(obj: any): string {
|
||||||
|
|
||||||
const keys = Object.keys(obj).sort();
|
const keys = Object.keys(obj).sort();
|
||||||
const keyValuePairs = keys.map(
|
const keyValuePairs = keys.map(
|
||||||
(key) => `"${key}":${customStringify(obj[key])}`,
|
(key) => `"${key}":${customStringify(obj[key])}`
|
||||||
);
|
);
|
||||||
return `{${keyValuePairs.join(",")}}`;
|
return `{${keyValuePairs.join(",")}}`;
|
||||||
}
|
}
|
||||||
|
|
@ -604,7 +603,7 @@ export function getHandleId(
|
||||||
source: string,
|
source: string,
|
||||||
sourceHandle: string,
|
sourceHandle: string,
|
||||||
target: string,
|
target: string,
|
||||||
targetHandle: string,
|
targetHandle: string
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
|
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
|
||||||
|
|
@ -615,7 +614,7 @@ export function generateFlow(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
name: string,
|
name: string
|
||||||
): generateFlowType {
|
): generateFlowType {
|
||||||
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
|
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
|
||||||
const uid = new ShortUniqueId({ length: 5 });
|
const uid = new ShortUniqueId({ length: 5 });
|
||||||
|
|
@ -624,7 +623,7 @@ export function generateFlow(
|
||||||
newFlowData.edges = selection.edges.filter(
|
newFlowData.edges = selection.edges.filter(
|
||||||
(edge) =>
|
(edge) =>
|
||||||
selection.nodes.some((node) => node.id === edge.target) &&
|
selection.nodes.some((node) => node.id === edge.target) &&
|
||||||
selection.nodes.some((node) => node.id === edge.source),
|
selection.nodes.some((node) => node.id === edge.source)
|
||||||
);
|
);
|
||||||
newFlowData.nodes = selection.nodes;
|
newFlowData.nodes = selection.nodes;
|
||||||
|
|
||||||
|
|
@ -645,7 +644,7 @@ export function generateFlow(
|
||||||
(edge) =>
|
(edge) =>
|
||||||
(selection.nodes.some((node) => node.id === edge.target) ||
|
(selection.nodes.some((node) => node.id === edge.target) ||
|
||||||
selection.nodes.some((node) => node.id === edge.source)) &&
|
selection.nodes.some((node) => node.id === edge.source)) &&
|
||||||
newFlowData.edges.every((e) => e.id !== edge.id),
|
newFlowData.edges.every((e) => e.id !== edge.id)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -656,13 +655,13 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
|
||||||
const { nodes, edges } = groupNode.data.node!.flow!.data!;
|
const { nodes, edges } = groupNode.data.node!.flow!.data!;
|
||||||
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
|
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
|
||||||
newEdges = newEdges.filter(
|
newEdges = newEdges.filter(
|
||||||
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id),
|
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id)
|
||||||
);
|
);
|
||||||
newEdges.forEach((edge) => {
|
newEdges.forEach((edge) => {
|
||||||
if (lastNode && edge.source === lastNode.id) {
|
if (lastNode && edge.source === lastNode.id) {
|
||||||
edge.source = groupNode.id;
|
edge.source = groupNode.id;
|
||||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||||
edge.sourceHandle!,
|
edge.sourceHandle!
|
||||||
);
|
);
|
||||||
newSourceHandle.id = groupNode.id;
|
newSourceHandle.id = groupNode.id;
|
||||||
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||||
|
|
@ -689,7 +688,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
|
||||||
export function filterFlow(
|
export function filterFlow(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
|
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
|
||||||
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
|
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
|
||||||
|
|
@ -727,7 +726,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
|
||||||
export function concatFlows(
|
export function concatFlows(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
const { nodes, edges } = flow.data!;
|
const { nodes, edges } = flow.data!;
|
||||||
setNodes((old) => [...old, ...nodes]);
|
setNodes((old) => [...old, ...nodes]);
|
||||||
|
|
@ -736,7 +735,7 @@ export function concatFlows(
|
||||||
|
|
||||||
export function validateSelection(
|
export function validateSelection(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
edges: Edge[],
|
edges: Edge[]
|
||||||
): Array<string> {
|
): Array<string> {
|
||||||
const clonedSelection = cloneDeep(selection);
|
const clonedSelection = cloneDeep(selection);
|
||||||
const clonedEdges = cloneDeep(edges);
|
const clonedEdges = cloneDeep(edges);
|
||||||
|
|
@ -750,7 +749,7 @@ export function validateSelection(
|
||||||
let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
|
let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
|
||||||
// then filter the edges that are connected to the nodes in the set
|
// then filter the edges that are connected to the nodes in the set
|
||||||
let connectedEdges = clonedSelection.edges.filter(
|
let connectedEdges = clonedSelection.edges.filter(
|
||||||
(e) => nodesSet.has(e.source) && nodesSet.has(e.target),
|
(e) => nodesSet.has(e.source) && nodesSet.has(e.target)
|
||||||
);
|
);
|
||||||
// add the edges to the selection
|
// add the edges to the selection
|
||||||
clonedSelection.edges = connectedEdges;
|
clonedSelection.edges = connectedEdges;
|
||||||
|
|
@ -764,17 +763,17 @@ export function validateSelection(
|
||||||
clonedSelection.nodes.some(
|
clonedSelection.nodes.some(
|
||||||
(node) =>
|
(node) =>
|
||||||
isInputNode(node.data as NodeDataType) ||
|
isInputNode(node.data as NodeDataType) ||
|
||||||
isOutputNode(node.data as NodeDataType),
|
isOutputNode(node.data as NodeDataType)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errorsArray.push(
|
errorsArray.push(
|
||||||
"Please select only nodes that are not input or output nodes",
|
"Please select only nodes that are not input or output nodes"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//check if there are two or more nodes with free outputs
|
//check if there are two or more nodes with free outputs
|
||||||
if (
|
if (
|
||||||
clonedSelection.nodes.filter(
|
clonedSelection.nodes.filter(
|
||||||
(n) => !clonedSelection.edges.some((e) => e.source === n.id),
|
(n) => !clonedSelection.edges.some((e) => e.source === n.id)
|
||||||
).length > 1
|
).length > 1
|
||||||
) {
|
) {
|
||||||
errorsArray.push("Please select only one node with free outputs");
|
errorsArray.push("Please select only one node with free outputs");
|
||||||
|
|
@ -785,7 +784,7 @@ export function validateSelection(
|
||||||
clonedSelection.nodes.some(
|
clonedSelection.nodes.some(
|
||||||
(node) =>
|
(node) =>
|
||||||
!clonedSelection.edges.some((edge) => edge.target === node.id) &&
|
!clonedSelection.edges.some((edge) => edge.target === node.id) &&
|
||||||
!clonedSelection.edges.some((edge) => edge.source === node.id),
|
!clonedSelection.edges.some((edge) => edge.source === node.id)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
errorsArray.push("Please select only nodes that are connected");
|
errorsArray.push("Please select only nodes that are connected");
|
||||||
|
|
@ -842,8 +841,8 @@ export function mergeNodeTemplates({
|
||||||
nodeTemplate[key].display_name
|
nodeTemplate[key].display_name
|
||||||
? nodeTemplate[key].display_name
|
? nodeTemplate[key].display_name
|
||||||
: nodeTemplate[key].name
|
: nodeTemplate[key].name
|
||||||
? toTitleCase(nodeTemplate[key].name)
|
? toTitleCase(nodeTemplate[key].name)
|
||||||
: toTitleCase(key);
|
: toTitleCase(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -854,7 +853,7 @@ function isHandleConnected(
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
key: string,
|
key: string,
|
||||||
field: TemplateVariableType,
|
field: TemplateVariableType,
|
||||||
nodeId: string,
|
nodeId: string
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
this function receives a flow and a handleId and check if there is a connection with this handle
|
this function receives a flow and a handleId and check if there is a connection with this handle
|
||||||
|
|
@ -870,7 +869,7 @@ function isHandleConnected(
|
||||||
id: nodeId,
|
id: nodeId,
|
||||||
proxy: { id: field.proxy!.id, field: field.proxy!.field },
|
proxy: { id: field.proxy!.id, field: field.proxy!.field },
|
||||||
inputTypes: field.input_types,
|
inputTypes: field.input_types,
|
||||||
} as targetHandleType),
|
} as targetHandleType)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -885,7 +884,7 @@ function isHandleConnected(
|
||||||
fieldName: key,
|
fieldName: key,
|
||||||
id: nodeId,
|
id: nodeId,
|
||||||
inputTypes: field.input_types,
|
inputTypes: field.input_types,
|
||||||
} as targetHandleType),
|
} as targetHandleType)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -908,7 +907,7 @@ export function generateNodeTemplate(Flow: FlowType) {
|
||||||
|
|
||||||
export function generateNodeFromFlow(
|
export function generateNodeFromFlow(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
getNodeId: (type: string) => string,
|
getNodeId: (type: string) => string
|
||||||
): NodeType {
|
): NodeType {
|
||||||
const { nodes } = flow.data!;
|
const { nodes } = flow.data!;
|
||||||
const outputNode = cloneDeep(findLastNode(flow.data!));
|
const outputNode = cloneDeep(findLastNode(flow.data!));
|
||||||
|
|
@ -939,7 +938,7 @@ export function generateNodeFromFlow(
|
||||||
export function connectedInputNodesOnHandle(
|
export function connectedInputNodesOnHandle(
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
handleId: string,
|
handleId: string,
|
||||||
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] },
|
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
|
||||||
) {
|
) {
|
||||||
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
|
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
|
||||||
[];
|
[];
|
||||||
|
|
@ -976,7 +975,7 @@ export function connectedInputNodesOnHandle(
|
||||||
|
|
||||||
export function updateProxyIdsOnTemplate(
|
export function updateProxyIdsOnTemplate(
|
||||||
template: APITemplateType,
|
template: APITemplateType,
|
||||||
idsMap: { [key: string]: string },
|
idsMap: { [key: string]: string }
|
||||||
) {
|
) {
|
||||||
Object.keys(template).forEach((key) => {
|
Object.keys(template).forEach((key) => {
|
||||||
if (template[key].proxy && idsMap[template[key].proxy!.id]) {
|
if (template[key].proxy && idsMap[template[key].proxy!.id]) {
|
||||||
|
|
@ -987,7 +986,7 @@ export function updateProxyIdsOnTemplate(
|
||||||
|
|
||||||
export function updateEdgesIds(
|
export function updateEdgesIds(
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
idsMap: { [key: string]: string },
|
idsMap: { [key: string]: string }
|
||||||
) {
|
) {
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
let targetHandle: targetHandleType = edge.data.targetHandle;
|
let targetHandle: targetHandleType = edge.data.targetHandle;
|
||||||
|
|
@ -1014,7 +1013,7 @@ export function expandGroupNode(
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
const idsMap = updateIds(flow!.data!);
|
const idsMap = updateIds(flow!.data!);
|
||||||
updateProxyIdsOnTemplate(template, idsMap);
|
updateProxyIdsOnTemplate(template, idsMap);
|
||||||
|
|
@ -1057,7 +1056,7 @@ export function expandGroupNode(
|
||||||
const lastNode = cloneDeep(findLastNode(flow!.data!));
|
const lastNode = cloneDeep(findLastNode(flow!.data!));
|
||||||
newEdge.source = lastNode!.id;
|
newEdge.source = lastNode!.id;
|
||||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||||
newEdge.sourceHandle!,
|
newEdge.sourceHandle!
|
||||||
);
|
);
|
||||||
newSourceHandle.id = lastNode!.id;
|
newSourceHandle.id = lastNode!.id;
|
||||||
newEdge.data.sourceHandle = newSourceHandle;
|
newEdge.data.sourceHandle = newSourceHandle;
|
||||||
|
|
@ -1114,7 +1113,7 @@ export function expandGroupNode(
|
||||||
|
|
||||||
export function getGroupStatus(
|
export function getGroupStatus(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
ssData: { [key: string]: { valid: boolean; params: string } },
|
ssData: { [key: string]: { valid: boolean; params: string } }
|
||||||
) {
|
) {
|
||||||
let status = { valid: true, params: SUCCESS_BUILD };
|
let status = { valid: true, params: SUCCESS_BUILD };
|
||||||
const { nodes } = flow.data!;
|
const { nodes } = flow.data!;
|
||||||
|
|
@ -1133,7 +1132,7 @@ export function getGroupStatus(
|
||||||
|
|
||||||
export function createFlowComponent(
|
export function createFlowComponent(
|
||||||
nodeData: NodeDataType,
|
nodeData: NodeDataType,
|
||||||
version: string,
|
version: string
|
||||||
): FlowType {
|
): FlowType {
|
||||||
const flowNode: FlowType = {
|
const flowNode: FlowType = {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1169,7 +1168,7 @@ export function downloadNode(NodeFLow: FlowType) {
|
||||||
|
|
||||||
export function updateComponentNameAndType(
|
export function updateComponentNameAndType(
|
||||||
data: any,
|
data: any,
|
||||||
component: NodeDataType,
|
component: NodeDataType
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
export function removeFileNameFromComponents(flow: FlowType) {
|
export function removeFileNameFromComponents(flow: FlowType) {
|
||||||
|
|
@ -1243,7 +1242,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) {
|
||||||
export function downloadFlow(
|
export function downloadFlow(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
flowName: string,
|
flowName: string,
|
||||||
flowDescription?: string,
|
flowDescription?: string
|
||||||
) {
|
) {
|
||||||
let clonedFlow = cloneDeep(flow);
|
let clonedFlow = cloneDeep(flow);
|
||||||
removeFileNameFromComponents(clonedFlow);
|
removeFileNameFromComponents(clonedFlow);
|
||||||
|
|
@ -1253,7 +1252,7 @@ export function downloadFlow(
|
||||||
...clonedFlow,
|
...clonedFlow,
|
||||||
name: flowName,
|
name: flowName,
|
||||||
description: flowDescription,
|
description: flowDescription,
|
||||||
}),
|
})
|
||||||
)}`;
|
)}`;
|
||||||
|
|
||||||
// create a link element and set its properties
|
// create a link element and set its properties
|
||||||
|
|
@ -1268,7 +1267,7 @@ export function downloadFlow(
|
||||||
export function downloadFlows() {
|
export function downloadFlows() {
|
||||||
downloadFlowsFromDatabase().then((flows) => {
|
downloadFlowsFromDatabase().then((flows) => {
|
||||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||||
JSON.stringify(flows),
|
JSON.stringify(flows)
|
||||||
)}`;
|
)}`;
|
||||||
|
|
||||||
// create a link element and set its properties
|
// create a link element and set its properties
|
||||||
|
|
@ -1292,7 +1291,7 @@ export function getRandomDescription(): string {
|
||||||
export const createNewFlow = (
|
export const createNewFlow = (
|
||||||
flowData: ReactFlowJsonObject,
|
flowData: ReactFlowJsonObject,
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
folderId: string,
|
folderId: string
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
description: flow?.description ?? getRandomDescription(),
|
description: flow?.description ?? getRandomDescription(),
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ import {
|
||||||
X,
|
X,
|
||||||
XCircle,
|
XCircle,
|
||||||
Zap,
|
Zap,
|
||||||
|
PlaySquare,
|
||||||
|
Wrench,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { FaApple, FaDiscord, FaGithub } from "react-icons/fa";
|
import { FaApple, FaDiscord, FaGithub } from "react-icons/fa";
|
||||||
import { AWSIcon } from "../icons/AWS";
|
import { AWSIcon } from "../icons/AWS";
|
||||||
|
|
@ -533,11 +535,12 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Command,
|
Command,
|
||||||
ArrowBigUp,
|
ArrowBigUp,
|
||||||
Dot,
|
Dot,
|
||||||
|
RotateCcw,
|
||||||
|
Wrench,
|
||||||
FolderPlusIcon,
|
FolderPlusIcon,
|
||||||
FolderIcon,
|
FolderIcon,
|
||||||
Discord: FaDiscord,
|
Discord: FaDiscord,
|
||||||
PaperclipIcon,
|
PaperclipIcon,
|
||||||
RotateCcw,
|
|
||||||
Settings,
|
Settings,
|
||||||
Streamlit,
|
Streamlit,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import { NodeType } from "../types/flow";
|
import { NodeType } from "../types/flow";
|
||||||
import { FlowState } from "../types/tabs";
|
import { FlowState } from "../types/tabs";
|
||||||
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
||||||
|
import { MODAL_CLASSES } from "../constants/constants";
|
||||||
|
|
||||||
export function classNames(...classes: Array<string>): string {
|
export function classNames(...classes: Array<string>): string {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
|
|
@ -406,3 +407,8 @@ export function extractColumnsFromRows(
|
||||||
|
|
||||||
return Object.values(columnsKeys);
|
return Object.values(columnsKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isThereModal(): boolean {
|
||||||
|
const modal = document.body.getElementsByClassName(MODAL_CLASSES);
|
||||||
|
return modal.length > 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
4
test-results/.last-run.json
Normal file
4
test-results/.last-run.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"status": "failed",
|
||||||
|
"failedTests": []
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue