Merge dev into types_refactor

This commit is contained in:
Igor Carvalho 2023-07-26 18:26:22 -03:00
commit 0d92a62759
69 changed files with 3960 additions and 4502 deletions

49
.github/workflows/pre-release.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: pre-release
on:
pull_request:
types:
- closed
branches:
- dev
paths:
- "pyproject.toml"
env:
POETRY_VERSION: "1.5.1"
jobs:
if_release:
if: |
${{ github.event.pull_request.merged == true }}
&& ${{ contains(github.event.pull_request.labels.*.name, 'pre-release') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry==$POETRY_VERSION
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "poetry"
- name: Build project for distribution
run: make build
- name: Check Version
id: check-version
run: |
echo version=$(poetry version --short) >> $GITHUB_OUTPUT
- name: Create Release
uses: ncipollo/release-action@v1
with:
artifacts: "dist/*"
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
generateReleaseNotes: true
tag: v${{ steps.check-version.outputs.version }}
commit: main
- name: Publish to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
run: |
poetry publish

View file

@ -10,7 +10,7 @@ on:
- "pyproject.toml"
env:
POETRY_VERSION: "1.4.0"
POETRY_VERSION: "1.5.1"
jobs:
if_release:

View file

@ -1,2 +1,82 @@
# Agents
(coming soon)
# Agents
Agents are components that use reasoning to make decisions and take actions, designed to autonomously perform tasks or provide services with some degree of “freedom” (or agency). They combine the power of LLM chaining processes with access to external tools such as APIs to interact with applications and accomplish tasks.
---
### AgentInitializer
The `AgentInitializer` component is a quick way to construct a zero-shot agent from a language model (LLM) and tools.
**Params**
- **LLM:** Language Model to use in the `AgentInitializer`.
- **Memory:** Used to add memory functionality to an agent. It allows the agent to store and retrieve information from previous conversations.
- **Tools:** Tools that the agent will have access to.
- **Agent:** The type of agent to be instantiated. Current supported: `zero-shot-react-description`, `react-docstore`, `self-ask-with-search,conversational-react-description` and `openai-functions`.
---
### CSVAgent
A `CSVAgent` is an agent that is designed to interact with CSV (Comma-Separated Values) files. CSV files are a common format for storing tabular data, where each row represents a record and each column represents a field. The CSV agent can perform various tasks, such as reading and writing CSV files, processing the data, and generating tables. It can extract information from the CSV file, manipulate the data, and perform operations like filtering, sorting, and aggregating.
**Params**
- **LLM:** Language Model to use in the `CSVAgent`.
- **path:** The file path to the CSV data.
---
### JSONAgent
The `JSONAgent` deals with JSON (JavaScript Object Notation) data. Similar to the CSVAgent, it works with a language model (LLM) and a toolkit designed for JSON manipulation. This agent can iteratively explore a JSON blob to find the information needed to answer the user's question. It can list keys, get values, and navigate through the structure of the JSON object.
**Params**
- **LLM:** Language Model to use in the `JSONAgent`.
- **Toolkit:** Toolkit that the agent will have access to.
---
### SQLAgent
A `SQLAgent` is an agent that is designed to interact with SQL databases. It is capable of performing various tasks, such as querying the database, retrieving data, and executing SQL statements. The agent can provide information about the structure of the database, including the tables and their schemas. It can also perform operations like inserting, updating, and deleting data in the database. The SQL agent is a helpful tool for managing and working with SQL databases efficiently.
**Params**
- **LLM:** Language Model to use in the `SQLAgent`.
- **database_uri:** A string representing the connection URI for the SQL database.
---
### VectorStoreAgent
The `VectorStoreAgent` is designed to work with a vector store a data structure used for storing and querying vector-based representations of data. The `VectorStoreAgent` can query the vector store to find relevant information based on user inputs.
**Params**
- **LLM:** Language Model to use in the `VectorStoreAgent`.
- **Vector Store Info:** `VectorStoreInfo` to use in the `VectorStoreAgent`.
---
### VectorStoreRouterAgent
The `VectorStoreRouterAgent` is a custom agent that takes a vector store router as input. It is typically used when theres a need to retrieve information from multiple vector stores. These can be connected through a `VectorStoreRouterToolkit` and sent over to the `VectorStoreRouterAgent`. An agent configured with multiple vector stores can route queries to the appropriate store based on the context.
**Params**
- **LLM:** Language Model to use in the `VectorStoreRouterAgent`.
- **Vector Store Router Toolkit:** `VectorStoreRouterToolkit` to use in the `VectorStoreRouterAgent`.
---
### ZeroShotAgent
The `ZeroShotAgent` is an agent that uses the ReAct framework to determine which tool to use based solely on the tool's description. It can be configured with any number of tools and requires a description for each tool. The agent is designed to be the most general-purpose action agent. It uses an `LLMChain` to determine which actions to take and in what order.
**Params**
- **Allowed Tools:** Tools that the agent will have access to.
- **LLM Chain:** LLM Chain to be used by the agent.

View file

@ -1,2 +1,137 @@
import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
# Chains
(coming soon)
Chains, in the context of language models, refer to a series of calls made to a language model. It allows for the output of one call to be used as the input for another call. Different types of chains allow for different levels of complexity. Chains are useful for creating pipelines and executing specific scenarios.
---
### CombineDocsChain
The `CombineDocsChain` incorporates methods to combine or aggregate loaded documents for question-answering functionality.
:::info
Works as a proxy of LangChains [documents](https://python.langchain.com/docs/modules/chains/document/) chains generated by the `load_qa_chain` function.
:::
**Params**
- **LLM:** Language Model to use in the chain.
- **chain_type:** The chain type to be used. Each one of them applies a different “combination strategy”.
- **stuff**: The stuff [documents](https://python.langchain.com/docs/modules/chains/document/stuff) chain (“stuff" as in "to stuff" or "to fill") is the most straightforward of *the* document chains. It takes a list of documents, inserts them all into a prompt, and passes that prompt to an LLM. This chain is well-suited for applications where documents are small and only a few are passed in for most calls.
- **map_reduce**: The map-reduce [documents](https://python.langchain.com/docs/modules/chains/document/map_reduce) chain first applies an LLM chain to each document individually (the Map step), treating the chain output as a new document. It then passes all the new documents to a separate combined documents chain to get a single output (the Reduce step). It can optionally first compress or collapse the mapped documents to make sure that they fit in the combined documents chain (which will often pass them to an LLM). This compression step is performed recursively if necessary.
- **map_rerank**: The map re-rank [documents](https://python.langchain.com/docs/modules/chains/document/map_rerank) chain runs an initial prompt on each document that not only tries to complete a task but also gives a score for how certain it is in its answer. The highest-scoring response is returned.
- **refine**: The refine [documents](https://python.langchain.com/docs/modules/chains/document/refine) chain constructs a response by looping over the input documents and iteratively updating its answer. For each document, it passes all non-document inputs, the current document, and the latest intermediate answer to an LLM chain to get a new answer.
Since the Refine chain only passes a single document to the LLM at a time, it is well-suited for tasks that require analyzing more documents than can fit in the model's context. The obvious tradeoff is that this chain will make far more LLM calls than, for example, the Stuff documents chain. There are also certain tasks that are difficult to accomplish iteratively. For example, the Refine chain can perform poorly when documents frequently cross-reference one another or when a task requires detailed information from many documents.
---
### ConversationChain
The `ConversationChain` is a straightforward chain for interactive conversations with a language model, making it ideal for chatbots or virtual assistants. It allows for dynamic conversations, question-answering, and complex dialogues.
**Params**
- **LLM:** Language Model to use in the chain.
- **Memory:** Default memory store.
- **input_key:** Used to specify the key under which the user input will be stored in the conversation memory. It allows you to provide the user's input to the chain for processing and generating a response.
- **output_key:** Used to specify the key under which the generated response will be stored in the conversation memory. It allows you to retrieve the response using the specified key.
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can be helpful for debugging and understanding the chain's behavior. If set to False, it will suppress the verbose output — defaults to `False`.
---
### ConversationalRetrievalChain
The `ConversationalRetrievalChain` extracts information and provides answers by combining document search and question-answering abilities.
:::info
A retriever is a component that finds documents based on a query. It doesn't store the documents themselves, but it returns the ones that match the query.
:::
**Params**
- **LLM:** Language Model to use in the chain.
- **Memory:** Default memory store.
- **Retriever:** The retriever used to fetch relevant documents.
- **chain_type:** The chain type to be used. Each one of them applies a different “combination strategy”.
- **stuff**: The stuff [documents](https://python.langchain.com/docs/modules/chains/document/stuff) chain (“stuff" as in "to stuff" or "to fill") is the most straightforward of *the* document chains. It takes a list of documents, inserts them all into a prompt, and passes that prompt to an LLM. This chain is well-suited for applications where documents are small and only a few are passed in for most calls.
- **map_reduce**: The map-reduce [documents](https://python.langchain.com/docs/modules/chains/document/map_reduce) chain first applies an LLM chain to each document individually (the Map step), treating the chain output as a new document. It then passes all the new documents to a separate combined documents chain to get a single output (the Reduce step). It can optionally first compress or collapse the mapped documents to make sure that they fit in the combined documents chain (which will often pass them to an LLM). This compression step is performed recursively if necessary.
- **map_rerank**: The map re-rank [documents](https://python.langchain.com/docs/modules/chains/document/map_rerank) chain runs an initial prompt on each document that not only tries to complete a task but also gives a score for how certain it is in its answer. The highest-scoring response is returned.
- **refine**: The refine [documents](https://python.langchain.com/docs/modules/chains/document/refine) chain constructs a response by looping over the input documents and iteratively updating its answer. For each document, it passes all non-document inputs, the current document, and the latest intermediate answer to an LLM chain to get a new answer.
Since the Refine chain only passes a single document to the LLM at a time, it is well-suited for tasks that require analyzing more documents than can fit in the model's context. The obvious tradeoff is that this chain will make far more LLM calls than, for example, the Stuff documents chain. There are also certain tasks that are difficult to accomplish iteratively. For example, the Refine chain can perform poorly when documents frequently cross-reference one another or when a task requires detailed information from many documents.
- **return_source_documents:** Used to specify whether or not to include the source documents that were used to answer the question in the output. When set to `True`, source documents will be included in the output along with the generated answer. This can be useful for providing additional context or references to the user — defaults to `True`.
- **verbose:** Whether or not to run in verbose mode. In verbose mode, intermediate logs will be printed to the console — defaults to `False`.
---
### LLMChain
The `LLMChain` is a straightforward chain that adds functionality around language models. It combines a prompt template with a language model. To use it, create input variables to format the prompt template. The formatted prompt is then sent to the language model, and the generated output is returned as the result of the `LLMChain`.
**Params**
- **LLM:** Language Model to use in the chain.
- **Memory:** Default memory store.
- **Prompt**: Prompt template object to use in the chain.
- **output_key:** This parameter is used to specify which key in the LLM output dictionary should be returned as the final output. By default, the `LLMChain` returns both the input and output key values — defaults to `text`.
- **verbose:** Whether or not to run in verbose mode. In verbose mode, intermediate logs will be printed to the console — defaults to `False`.
---
### LLMMathChain
The `LLMMathChain` combines a language model (LLM) and a math calculation component. It allows the user to input math problems and get the corresponding solutions.
The `LLMMathChain` works by using the language model with an `LLMChain` to understand the input math problem and generate a math expression. It then passes this expression to the math component, which evaluates it and returns the result.
**Params**
- **LLM:** Language Model to use in the chain.
- **LLMChain:** LLM Chain to use in the chain.
- **Memory:** Default memory store.
- **input_key:** Used to specify the input value for the mathematical calculation. It allows you to provide the specific values or variables that you want to use in the calculation — defaults to `question`.
- **output_key:** Used to specify the key under which the output of the mathematical calculation will be stored. It allows you to retrieve the result of the calculation using the specified key — defaults to `answer`.
- **verbose:** Whether or not to run in verbose mode. In verbose mode, intermediate logs will be printed to the console — defaults to `False`.
---
### RetrievalQA
`RetrievalQA` is a chain used to find relevant documents or information to answer a given query. The retriever is responsible for returning the relevant documents based on the query, and the QA component then extracts the answer from those documents. The retrieval QA system combines the capabilities of both the retriever and the QA component to provide accurate and relevant answers to user queries.
:::info
A retriever is a component that finds documents based on a query. It doesn't store the documents themselves, but it returns the ones that match the query.
:::
**Params**
- **Combine Documents Chain:** Chain to use to combine the documents.
- **Memory:** Default memory store.
- **Retriever:** The retriever used to fetch relevant documents.
- **input_key:** This parameter is used to specify the key in the input data that contains the question. It is used to retrieve the question from the input data and pass it to the question-answering model for generating the answer — defaults to `query`.
- **output_key:** This parameter is used to specify the key in the output data where the generated answer will be stored. It is used to retrieve the answer from the output data after the question-answering model has generated it — defaults to `result`.
- **return_source_documents:** Used to specify whether or not to include the source documents that were used to answer the question in the output. When set to `True`, source documents will be included in the output along with the generated answer. This can be useful for providing additional context or references to the user — defaults to `True`.
- **verbose:** Whether or not to run in verbose mode. In verbose mode, intermediate logs will be printed to the console — defaults to `False`.
---
### SQLDatabaseChain
The `SQLDatabaseChain` finds answers to questions using a SQL database. It works by using the language model to understand the SQL query and generate the corresponding SQL code. It then passes the SQL code to the SQL database component, which executes the query on the database and returns the result.
**Params**
- **Db:** SQL Database to connect to.
- **LLM:** Language Model to use in the chain.
- **Prompt:** Prompt template to translate natural language to SQL.

View file

@ -1,2 +1,67 @@
# Embeddings
(coming soon)
Embeddings are vector representations of text that capture the semantic meaning of the text. They are created using text embedding models and allow us to think about the text in a vector space, enabling us to perform tasks like semantic search, where we look for pieces of text that are most similar in the vector space.
---
### CohereEmbeddings
Used to load [Coheres](https://cohere.com/) embedding models.
**Params**
- **cohere_api_key:** Holds the API key required to authenticate with the Cohere service.
- **model:** The language model used for embedding text documents and performing queries —defaults to `embed-english-v2.0`.
- **truncate:** Used to specify whether or not to truncate the input text. Truncation is useful when dealing with long texts that exceed the model's maximum input length. By truncating the text, the user can ensure that it fits within the model's constraints.
---
### HuggingFaceEmbeddings
Used to load [HuggingFaces](https://huggingface.co) embedding models.
**Params**
- **cache_folder:** Used to specify the folder where the embeddings will be cached. When embeddings are computed for a text, they can be stored in the cache folder so that they can be reused later without the need to recompute them. This can improve the performance of the application by avoiding redundant computations.
- **encode_kwargs:** Used to pass additional keyword arguments to the encoding method of the underlying HuggingFace model. These keyword arguments can be used to customize the encoding process, such as specifying the maximum length of the input sequence or enabling truncation or padding.
- **model_kwargs:** Used to customize the behavior of the model, such as specifying the model architecture, the tokenizer, or any other model-specific configuration options. By using `model_kwargs`, the user can configure the HuggingFace model according to specific needs and preferences.
- **model_name:** Used to specify the name or identifier of the HuggingFace model that will be used for generating embeddings. It allows users to choose a specific pre-trained model from the Hugging Face model hub — defaults to `sentence-transformers/all-mpnet-base-v2`.
---
### OpenAIEmbeddings
Used to load [OpenAIs](https://openai.com/) embedding models.
**Params**
- **chunk_size:** Determines the maximum size of each chunk of text that is processed for embedding. If any of the incoming text chunks exceeds `chunk_size` characters, it will be split into multiple chunks of size `chunk_size` or less before being embedded — defaults to `1000`.
- **deployment:** Used to specify the deployment name or identifier of the text embedding model. It allows the user to choose a specific deployment of the model to use for embedding. When the deployment is provided, this can be useful when the user has multiple deployments of the same model with different configurations or versions — defaults to `text-embedding-ada-002`.
- **embedding_ctx_length:** This parameter determines the maximum context length for the text embedding model. It specifies the number of tokens that the model considers when generating embeddings for a piece of text — defaults to `8191` (this means that the model will consider up to 8191 tokens when generating embeddings).
- **max_retries:** Determines the maximum number of times to retry a request if the model provider returns an error from their API — defaults to `6`.
- **model:** Defines which pre-trained text embedding model to use — defaults to `text-embedding-ada-002`.
- **openai_api_base:** Refers to the base URL for the Azure OpenAI resource. It is used to configure the API to connect to the Azure OpenAI service. The base URL can be found in the Azure portal under the user Azure OpenAI resource.
- **openai_api_key:** Is used to authenticate and authorize access to the OpenAI service.
- **openai_api_type:** Is used to specify the type of OpenAI API being used, either the regular OpenAI API or the Azure OpenAI API. This parameter allows the `OpenAIEmbeddings` class to connect to the appropriate API service.
- **openai_api_version:** Is used to specify the version of the OpenAI API being used. This parameter allows the `OpenAIEmbeddings` class to connect to the appropriate version of the OpenAI API service.
- **openai_organization:** Is used to specify the organization associated with the OpenAI API key. If not provided, the default organization associated with the API key will be used.
- **openai_proxy:** Proxy enables better budgeting and cost management for making OpenAI API calls, including more transparency into pricing.
- **request_timeout:** Used to specify the maximum amount of time, in milliseconds, to wait for a response from the OpenAI API when generating embeddings for a given text.
- **tiktoken_model_name:** Used to count the number of tokens in documents to constrain them to be under a certain limit. By default, when set to None, this will be the same as the embedding model name.

View file

@ -1,2 +1,15 @@
# Prompts
(coming soon)
A prompt refers to the input given to a language model. It is constructed from multiple components and can be parametrized using prompt templates. A prompt template is a reproducible way to generate prompts and allow for easy customization through input variables.
---
### PromptTemplate
The `PromptTemplate` component allows users to create prompts and define variables that provide control over instructing the model. The template can take in a set of variables from the end user and generates the prompt once the conversation is initiated.
:::info
Once a variable is defined in the prompt template, it becomes a component input of its own. Check out [Prompt Customization](../guidelines/prompt-customization.mdx) to learn more.
:::
- **template:** Template used to format an individual request.

View file

@ -23,6 +23,25 @@ module.exports = {
"guidelines/chat-interface",
],
},
{
type: "category",
label: "Component Reference",
collapsed: false,
items: [
"components/agents",
"components/chains",
"components/embeddings",
"components/llms",
"components/loaders",
"components/memories",
"components/prompts",
"components/text-splitters",
"components/toolkits",
"components/tools",
"components/vector-stores",
"components/wrappers",
],
},
{
type: "category",
label: "Step-by-Step Guides",

857
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,17 @@
[tool.poetry]
name = "langflow"
version = "0.3.0"
version = "0.3.3"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [
"Carlos Coelho <carlos@logspace.ai>",
"Cristhian Zanforlin <cristhian.lousa@gmail.com>",
"Gabriel Almeida <gabriel@logspace.ai>",
"Gustavo Schaedler <gustavopoa@gmail.com>",
"Igor Carvalho <igorr.ackerman@gmail.com>",
"Lucas Eduoli <lucaseduoli@gmail.com>",
"Otávio Anovazzi <otavio2204@gmail.com>",
"Rodrigo Nader <rodrigo@logspace.ai>",
]
repository = "https://github.com/logspace-ai/langflow"
license = "MIT"
@ -29,8 +32,8 @@ beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0"
typer = "^0.9.0"
gunicorn = "^20.1.0"
langchain = "^0.0.232"
gunicorn = "^21.1.0"
langchain = "^0.0.240"
openai = "^0.27.8"
pandas = "^2.0.0"
chromadb = "^0.3.21"

View file

@ -134,6 +134,7 @@ async def stream_build(flow_id: str):
# to set the input_keys values
artifacts.update(vertex.artifacts)
except Exception as exc:
logger.exception(exc)
params = str(exc)
valid = False
flow_data_store[flow_id]["status"] = BuildStatus.FAILURE

View file

@ -176,6 +176,8 @@ class Vertex:
for node in nodes:
built = node.build()
if isinstance(built, list):
if key not in self.params:
self.params[key] = []
self.params[key].extend(built)
else:
self.params[key].append(built)

View file

@ -98,7 +98,7 @@ class DocumentLoaderVertex(Vertex):
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
\nAvg. Document Length (characters): {int(avg_length)}
Documents: {self._built_object[:3]}..."""
return f"{self.vertex_type}()"
@ -136,7 +136,7 @@ class TextSplitterVertex(Vertex):
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
\nAvg. Document Length (characters): {int(avg_length)}
\nDocuments: {self._built_object[:3]}..."""
return f"{self.vertex_type}()"

View file

@ -59,15 +59,20 @@ def setup_static_files(app: FastAPI, static_files_dir: Path):
return FileResponse(path)
# app = create_app()
# setup_static_files(app, static_files_dir)
def get_static_files_dir():
"""Get the static files directory relative to Langflow's main.py file."""
frontend_path = Path(__file__).parent
return frontend_path / "frontend"
def setup_app(static_files_dir: Optional[Path] = None) -> FastAPI:
"""Setup the FastAPI app."""
# get the directory of the current file
if not static_files_dir:
frontend_path = Path(__file__).parent
static_files_dir = frontend_path / "frontend"
static_files_dir = get_static_files_dir()
if not static_files_dir or not static_files_dir.exists():
raise RuntimeError(f"Static files directory {static_files_dir} does not exist.")
app = create_app()
setup_static_files(app, static_files_dir)
return app

View file

@ -8,6 +8,7 @@ FORCE_SHOW_FIELDS = [
"headers",
"max_value_length",
"max_tokens",
"google_cse_id",
]
DEFAULT_PROMPT = """

View file

@ -15,6 +15,7 @@ class TextSplittersFrontendNode(FrontendNode):
required=True,
show=True,
name="documents",
is_list=True,
)
)
name = "separator"

View file

@ -259,7 +259,7 @@ class VectorStoreFrontendNode(FrontendNode):
field.required = False
field.show = True
field.advanced = False
field.is_list = True
elif "embedding" in field.name:
# for backwards compatibility
field.name = "embedding"

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0",
"add": "^2.0.6",

View file

@ -1,3 +1,4 @@
import { cloneDeep } from "lodash";
import React, { useContext, useEffect, useRef, useState } from "react";
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
@ -13,14 +14,10 @@ import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants/constants";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import {
cleanEdges,
isValidConnection,
} from "../../../../utils/reactflowUtils";
import { isValidConnection } from "../../../../utils/reactflowUtils";
import {
nodeColors,
nodeIconsLucide,
@ -36,6 +33,7 @@ export default function ParameterComponent({
left,
id,
data,
setData,
tooltipTitle,
title,
color,
@ -51,7 +49,6 @@ export default function ParameterComponent({
const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { closePopUp } = useContext(PopUpContext);
const { setTabsState, tabId, save } = useContext(TabsContext);
// Update component position
@ -66,15 +63,16 @@ export default function ParameterComponent({
updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]);
useEffect(() => {}, [closePopUp, data.node.template]);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
const [myData, setMyData] = useState(useContext(typesContext).data);
const { data: myData } = useContext(typesContext);
const handleOnNewValue = (newValue: any) => {
data.node.template[name].value = newValue;
let newData = cloneDeep(data);
newData.node.template[name].value = newValue;
setData(newData);
// Set state to pending
setTabsState((prev) => {
return {
@ -244,7 +242,6 @@ export default function ParameterComponent({
) : (
<InputComponent
disabled={disabled}
disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
@ -266,7 +263,6 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
@ -311,7 +307,6 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<IntComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
@ -322,15 +317,6 @@ export default function ParameterComponent({
field_name={name}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
if (reactFlowInstance) {
cleanEdges({
flow: {
edges: reactFlowInstance.getEdges(),
nodes: reactFlowInstance.getNodes(),
},
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
});
}
}}
nodeClass={data.node}
disabled={disabled}

View file

@ -1,30 +1,33 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useRef, useState } from "react";
import { NodeToolbar } from "reactflow";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { useSSE } from "../../contexts/SSEContext";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { NodeDataType } from "../../types/flow";
import { cleanEdges } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, toTitleCase } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
data,
data: olddata,
selected,
}: {
data: NodeDataType;
selected: boolean;
}): JSX.Element {
const [data, setData] = useState(olddata);
const { setErrorData } = useContext(alertContext);
const { updateFlow, flows, tabId } = useContext(TabsContext);
const updateNodeInternals = useUpdateNodeInternals();
const showError = useRef(true);
const { types, deleteNode } = useContext(typesContext);
const { closePopUp, openPopUp } = useContext(PopUpContext);
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
// any to avoid type conflict
// PROBLEM HERE OTAVIO
const Icon: any =
@ -33,7 +36,26 @@ export default function GenericNode({
const [validationStatus, setValidationStatus] = useState(null);
// State for outline color
const { sseData, isBuilding } = useSSE();
const refHtml = useRef(null);
useEffect(() => {
olddata.node = data.node;
let myFlow = flows.find((flow) => flow.id === tabId);
if (reactFlowInstance && myFlow) {
let flow = cloneDeep(myFlow);
flow.data = reactFlowInstance.toObject();
cleanEdges({
flow: {
edges: flow.data.edges,
nodes: flow.data.nodes,
},
updateEdge: (edge) => {
flow.data.edges = edge;
reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id);
},
});
updateFlow(flow);
}
}, [data]);
// New useEffect to watch for changes in sseData and update validation status
useEffect(() => {
@ -58,13 +80,12 @@ export default function GenericNode({
deleteNode(data.id);
return;
}
useEffect(() => {}, [closePopUp, data.node.template]);
return (
<>
<NodeToolbar>
<NodeToolbarComponent
data={data}
openPopUp={openPopUp}
setData={setData}
deleteNode={deleteNode}
></NodeToolbarComponent>
</NodeToolbar>
@ -90,7 +111,6 @@ export default function GenericNode({
</ShadTooltip>
</div>
</div>
<div className="round-button-div"></div>
<div className="round-button-div">
<div>
<Tooltip
@ -162,6 +182,7 @@ export default function GenericNode({
!data.node.template[t].advanced ? (
<ParameterComponent
data={data}
setData={setData}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors[data.node.template[t].type] ??
@ -208,6 +229,7 @@ export default function GenericNode({
</div>
<ParameterComponent
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node.output_types && data.node.output_types.length > 0

View file

@ -27,7 +27,13 @@ export const EditFlowSettings: React.FC<InputProps> = ({
setName(value);
};
const [desc, setDesc] = useState(
flows.find((f) => f.id === tabId).description
);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setDesc(flows.find((f) => f.id === tabId).description);
setDescription(event.target.value);
};
@ -41,7 +47,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
)}
</div>
<Input
className="mt-2 font-normal"
className="nopan nodrag noundo nocopy mt-2 font-normal"
onChange={handleNameChange}
type="text"
name="name"
@ -52,12 +58,15 @@ export const EditFlowSettings: React.FC<InputProps> = ({
/>
</Label>
<Label>
<span className="font-medium">Description (optional)</span>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium ">Description (optional)</span>
</div>
<Textarea
name="description"
id="description"
onChange={handleDescriptionChange}
value={description ?? ""}
value={desc}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}

View file

@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { useEffect, useState } from "react";
import CodeAreaModal from "../../modals/codeAreaModal";
import { TextAreaComponentType } from "../../types/components";
@ -16,7 +15,6 @@ export default function CodeAreaComponent({
const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
);
const { openPopUp } = useContext(PopUpContext);
useEffect(() => {
if (disabled) {
setMyValue("");
@ -30,45 +28,26 @@ export default function CodeAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<CodeAreaModal
value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
}}
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable input-ring " : "") +
" input-primary text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type something..."}
</span>
<button
onClick={() => {
openPopUp(
<CodeAreaModal
setNodeClass={setNodeClass}
value={myValue}
nodeClass={nodeClass}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
}}
>
<CodeAreaModal
value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
>
<div className="flex w-full items-center">
<span
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable input-ring " : "") +
" input-primary text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type something..."}
</span>
{!editNode && (
<IconComponent
name="ExternalLink"
@ -78,8 +57,8 @@ export default function CodeAreaComponent({
}
/>
)}
</button>
</div>
</div>
</CodeAreaModal>
</div>
);
}

View file

@ -0,0 +1,667 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import AccordionComponent from "../../components/AccordionComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
import InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
import PromptAreaComponent from "../../components/promptComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import { darkContext } from "../../contexts/darkContext";
import { FlowType } from "../../types/flow/index";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
export default function CodeTabsComponent({
flow,
tabs,
activeTab,
setActiveTab,
isMessage,
tweaks,
}: {
flow?: FlowType;
tabs: any;
activeTab: string;
setActiveTab: any;
isMessage?: boolean;
tweaks?: {
tweak?: any;
tweaksList?: any;
buildContent?: any;
getValue?: any;
buildTweakObject?: any;
};
}) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState([]);
const { dark } = useContext(darkContext);
useEffect(() => {
if (flow && flow["data"]["nodes"]) {
setData(flow["data"]["nodes"]);
}
}, [flow]);
const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
const downloadAsFile = () => {
const fileExtension = tabs[activeTab].language || ".txt";
const suggestedFileName = `${"generated-code."}${fileExtension}`;
const fileName = window.prompt("Enter the file name.", suggestedFileName);
if (!fileName) {
// user pressed cancel on prompt
return;
}
const blob = new Blob([tabs[activeTab].code], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
function openAccordions() {
let accordionsToOpen = [];
tweaks.tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen);
}
});
});
if (accordionsToOpen.length == 0) {
setOpenAccordion([]);
}
}
return (
<Tabs
value={activeTab}
className={
"api-modal-tabs " +
(isMessage ? "dark " : "") +
(dark && isMessage ? "bg-background" : "")
}
onValueChange={(value) => {
setActiveTab(value);
if (value === "3") {
openAccordions();
}
}}
>
<div className="api-modal-tablist-div">
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger
className={
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
}
key={index}
value={index.toString()}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{Number(activeTab) < 3 && (
<div className="float-right mx-1 flex gap-2">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
>
{isCopied ? (
<IconComponent name="Check" className="h-4 w-4" />
) : (
<IconComponent name="Clipboard" className="h-4 w-4" />
)}
{isCopied ? "Copied!" : "Copy code"}
</button>
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={downloadAsFile}
>
<IconComponent name="Download" className="h-5 w-5" />
</button>
</div>
)}
</div>
{tabs.map((tab, index) => (
<TabsContent
value={index.toString()}
className="api-modal-tabs-content"
key={index} // Remember to add a unique key prop
>
{index < 3 ? (
<SyntaxHighlighter
className="mt-0 h-full w-full overflow-auto custom-scroll"
language={tab.mode}
style={oneDark}
>
{tab.code}
</SyntaxHighlighter>
) : index === 3 ? (
<>
<div className="api-modal-according-display">
<div
className={classNames(
"h-[70vh] w-full rounded-lg bg-muted",
1 == 1
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
{data.map((t: any, index) => (
<div className="px-3" key={index}>
{tweaks.tweaksList.current.includes(t["data"]["id"]) && (
<AccordionComponent
trigger={t["data"]["id"]}
open={openAccordion}
keyValue={t["data"]["id"]}
>
<div className="api-modal-table-arrangement">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="h-10 border-input text-xs font-medium text-ring">
<TableRow className="dark:border-b-muted">
<TableHead className="h-7 text-center">
PARAM
</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type ===
"bool" ||
t.data.node.template[n].type ===
"float" ||
t.data.node.template[n].type ===
"code" ||
t.data.node.template[n].type ===
"prompt" ||
t.data.node.template[n].type ===
"file" ||
t.data.node.template[n].type === "int")
)
.map((n, i) => {
return (
<TableRow
key={i}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-foreground">
{n}
</TableCell>
<TableCell className="p-0 text-xs text-foreground">
<div className="m-auto w-[250px]">
{t.data.node.template[n].type ===
"str" &&
!t.data.node.template[n].options ? (
<div className="mx-auto">
{t.data.node.template[n]
.list ? (
<InputListComponent
editNode={true}
disabled={false}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? [""]
: t.data.node.template[
n
].value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
) : t.data.node.template[n]
.multiline ? (
<ShadTooltip
content={tweaks.buildContent(
t.data.node.template[n]
.value
)}
>
<div>
<TextAreaComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[
n
].value ||
t.data.node.template[
n
].value === ""
? ""
: t.data.node
.template[n]
.value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : (
<InputComponent
editNode={true}
disabled={false}
password={
t.data.node.template[n]
.password ?? false
}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[
n
].value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
)}
</div>
) : t.data.node.template[n].type ===
"bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
enabled={
t.data.node.template[n]
.value
}
setEnabled={(e) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = e;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
e,
t.data.node.template[n]
);
}}
size="small"
disabled={false}
/>
</div>
) : t.data.node.template[n].type ===
"file" ? (
<ShadTooltip
content={tweaks.buildContent(
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[n]
.value
)}
>
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={false}
value={
t.data.node.template[n]
.value ?? ""
}
onChange={(k: any) => {}}
fileTypes={
t.data.node.template[n]
.fileTypes
}
suffixes={
t.data.node.template[n]
.suffixes
}
onFileChange={(
k: any
) => {}}
></InputFileComponent>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"float" ? (
<div className="mx-auto">
<FloatComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[n]
.value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
</div>
) : t.data.node.template[n].type ===
"str" &&
t.data.node.template[n]
.options ? (
<div className="mx-auto">
<Dropdown
editNode={true}
apiModal={true}
options={
t.data.node.template[n]
.options
}
onSelect={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[n]
.value
}
></Dropdown>
</div>
) : t.data.node.template[n].type ===
"int" ? (
<div className="mx-auto">
<IntComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[n]
.value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
</div>
) : t.data.node.template[n].type ===
"prompt" ? (
<ShadTooltip
content={tweaks.buildContent(
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[n]
.value
)}
>
<div className="mx-auto">
<PromptAreaComponent
editNode={true}
disabled={false}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[
n
].value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"code" ? (
<ShadTooltip
content={tweaks.buildContent(
tweaks.getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<CodeAreaComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
? ""
: t.data.node.template[
n
].value
}
onChange={(k) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
index
].data.node.template[
n
].value = k;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"Any" ? (
"-"
) : (
<div className="hidden"></div>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</AccordionComponent>
)}
{tweaks.tweaksList.current.length === 0 && (
<>
<div className="pt-3">
No tweaks are available for this flow.
</div>
</>
)}
</div>
))}
</div>
</div>
</>
) : null}
</TabsContent>
))}
</Tabs>
);
}

View file

@ -1,6 +1,5 @@
import { Listbox, Transition } from "@headlessui/react";
import { Fragment, useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { Fragment, useEffect, useState } from "react";
import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
@ -13,15 +12,13 @@ export default function Dropdown({
numberOfOptions = 0,
apiModal = false,
}: DropDownComponentType): JSX.Element {
const { closePopUp } = useContext(PopUpContext);
let [internalValue, setInternalValue] = useState(
value === "" || !value ? "Choose an option" : value
);
useEffect(() => {
setInternalValue(value === "" || !value ? "Choose an option" : value);
}, [closePopUp]);
}, [value]);
return (
<>

View file

@ -1,19 +1,13 @@
import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { useEffect } from "react";
import { FloatComponentType } from "../../types/components";
import { Input } from "../ui/input";
export default function FloatComponent({
value,
onChange,
disableCopyPaste = false,
disabled,
editNode = false,
}: FloatComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value ?? "");
const { setDisableCopyPaste } = useContext(TabsContext);
const { closePopUp } = useContext(PopUpContext);
const step = 0.1;
const min = 0;
const max = 1;
@ -21,24 +15,13 @@ export default function FloatComponent({
// Clear component state
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
useEffect(() => {
setMyValue(value);
}, [closePopUp]);
return (
<div className={"w-full " + (disabled ? "float-component-pointer" : "")}>
<input
onFocus={() => {
if (disableCopyPaste) setDisableCopyPaste(true);
}}
onBlur={() => {
if (disableCopyPaste) setDisableCopyPaste(false);
}}
<div className="w-full">
<Input
type="number"
step={step}
min={min}
@ -51,17 +34,13 @@ export default function FloatComponent({
}
}}
max={max}
value={myValue}
className={
editNode
? "input-edit-node"
: "input-primary" + (disabled ? " input-disable " : "")
}
value={value ?? ""}
disabled={disabled}
className={editNode ? "input-edit-node" : ""}
placeholder={
editNode ? "Number 0 to 1" : "Type a number from zero to one"
}
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>

View file

@ -1,5 +1,4 @@
import { useContext } from "react";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { useContext, useState } from "react";
import { TabsContext } from "../../../../contexts/tabsContext";
import {
DropdownMenu,
@ -18,10 +17,10 @@ import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
const { updateFlow, setTabId, addFlow } = useContext(TabsContext);
const { addFlow } = useContext(TabsContext);
const { setErrorData } = useContext(alertContext);
const { openPopUp } = useContext(PopUpContext);
const { undo, redo } = useContext(undoRedoContext);
const [openSettings, setOpenSettings] = useState(false);
const navigate = useNavigate();
@ -63,9 +62,10 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
openPopUp(<FlowSettingsModal />);
setOpenSettings(true);
}}
className="cursor-pointer"
>
@ -75,6 +75,7 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
/>
Settings
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
undo();
@ -95,6 +96,10 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<FlowSettingsModal
open={openSettings}
setOpen={setOpenSettings}
></FlowSettingsModal>
</div>
</div>
);

View file

@ -1,56 +1,37 @@
import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { useEffect, useState } from "react";
import { InputComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
export default function InputComponent({
value,
onChange,
disableCopyPaste = false,
disabled,
password,
editNode = false,
}: InputComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value ?? "");
const [pwdVisible, setPwdVisible] = useState(false);
const { setDisableCopyPaste } = useContext(TabsContext);
const { closePopUp } = useContext(PopUpContext);
// Clear component state
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
useEffect(() => {
setMyValue(value ?? "");
}, [closePopUp]);
return (
<div className={disabled ? "input-component-div" : "relative"}>
<input
value={myValue}
onFocus={() => {
if (disableCopyPaste) setDisableCopyPaste(true);
}}
onBlur={() => {
if (disableCopyPaste) setDisableCopyPaste(false);
}}
<div className="relative w-full">
<Input
value={value}
disabled={disabled}
className={classNames(
disabled ? " input-disable " : "",
password && !pwdVisible && myValue !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : " input-primary ",
password && !pwdVisible && value !== "" ? " text-clip password " : "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)}
placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>

View file

@ -1,8 +1,9 @@
import { useContext, useEffect, useState } from "react";
import { useEffect } from "react";
import { InputListComponentType } from "../../types/components";
import { Input } from "../ui/input";
import { classNames } from "../../utils/utils";
import _ from "lodash";
import { PopUpContext } from "../../contexts/popUpContext";
import IconComponent from "../genericIconComponent";
export default function InputListComponent({
@ -10,59 +11,42 @@ export default function InputListComponent({
onChange,
disabled,
editNode = false,
onAddInput,
}: InputListComponentType): JSX.Element {
const [inputList, setInputList] = useState(value ?? [""]);
const { closePopUp } = useContext(PopUpContext);
useEffect(() => {
if (disabled) {
setInputList([""]);
onChange([""]);
}
}, [disabled, onChange]);
useEffect(() => {
setInputList(value);
}, [closePopUp]);
}, [disabled]);
return (
<div
className={
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
"flex flex-col gap-3"
}
>
{inputList.map((i, idx) => {
<div
className={
classNames(
value.length > 1 && editNode ? "my-1" : "",
"flex flex-col gap-3"
)
}>
{value.map((i, idx) => {
return (
<div key={idx} className="flex w-full gap-3">
<input
<Input
disabled={disabled}
type="text"
value={i}
className={
editNode
? "input-edit-node "
: "input-primary " + (disabled ? "input-disable" : "")
}
className={editNode ? "input-edit-node" : ""}
placeholder="Type something..."
onChange={(e) => {
setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList[idx] = e.target.value;
return newInputList;
});
onChange(inputList);
let newInputList = _.cloneDeep(value);
newInputList[idx] = e.target.value;
onChange(newInputList);
}}
/>
{idx === inputList.length - 1 ? (
{idx === value.length - 1 ? (
<button
onClick={() => {
setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList.push("");
return newInputList;
});
onChange(inputList);
let newInputList = _.cloneDeep(value);
newInputList.push("");
onChange(newInputList);
}}
>
<IconComponent
@ -73,12 +57,9 @@ export default function InputListComponent({
) : (
<button
onClick={() => {
setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList.splice(idx, 1);
return newInputList;
});
onChange(inputList);
let newInputList = _.cloneDeep(value);
newInputList.splice(idx, 1);
onChange(newInputList);
}}
>
<IconComponent

View file

@ -1,46 +1,25 @@
import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { useEffect } from "react";
import { FloatComponentType } from "../../types/components";
import { Input } from "../ui/input";
export default function IntComponent({
value,
onChange,
disableCopyPaste = false,
disabled,
editNode = false,
}: FloatComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value ?? "");
const { setDisableCopyPaste } = useContext(TabsContext);
const min = 0;
const { closePopUp } = useContext(PopUpContext);
// Clear component state
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
useEffect(() => {
setMyValue(value);
}, [closePopUp]);
return (
<div
className={
"w-full " +
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "")
}
>
<input
onFocus={() => {
if (disableCopyPaste) setDisableCopyPaste(true);
}}
onBlur={() => {
if (disableCopyPaste) setDisableCopyPaste(false);
}}
<div className="w-full">
<Input
onKeyDown={(event) => {
if (
event.key !== "Backspace" &&
@ -67,15 +46,11 @@ export default function IntComponent({
e.target.value = min.toString();
}
}}
value={myValue}
className={
editNode
? " input-edit-node "
: " input-primary " + (disabled ? " input-disable" : "")
}
value={value ?? ""}
className={editNode ? "input-edit-node" : ""}
disabled={disabled}
placeholder={editNode ? "Integer number" : "Type an integer number"}
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>

View file

@ -1,8 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { useEffect } from "react";
import { TypeModal } from "../../constants/enums";
import { PopUpContext } from "../../contexts/popUpContext";
import { typesContext } from "../../contexts/typesContext";
import { postValidatePrompt } from "../../controllers/API";
import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
@ -17,18 +15,13 @@ export default function PromptAreaComponent({
disabled,
editNode = false,
}: TextAreaComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value);
const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
}, [disabled]);
useEffect(() => {
setMyValue(value);
if (value !== "" && !editNode) {
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
if (apiReturn.data) {
@ -37,56 +30,32 @@ export default function PromptAreaComponent({
}
});
}
}, [value, reactFlowInstance]);
}, []);
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable text-ring " : "") +
" input-primary text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
<GenericModal
type={TypeModal.PROMPT}
value={value}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
>
<div className="flex w-full items-center">
<span
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable text-ring " : "") +
" input-primary text-muted-foreground "
}
>
{value !== "" ? value : "Type your prompt here..."}
</span>
{!editNode && (
<IconComponent
name="ExternalLink"
@ -96,8 +65,8 @@ export default function PromptAreaComponent({
}
/>
)}
</button>
</div>
</div>
</GenericModal>
</div>
);
}

View file

@ -1,10 +1,9 @@
import { useContext, useEffect, useState } from "react";
import { useEffect } from "react";
import { TypeModal } from "../../constants/enums";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Input } from "../ui/input";
export default function TextAreaComponent({
value,
@ -12,59 +11,32 @@ export default function TextAreaComponent({
disabled,
editNode = false,
}: TextAreaComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value);
const { openPopUp, closePopUp } = useContext(PopUpContext);
const { setDisableCopyPaste } = useContext(TabsContext);
// Clear text area
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
useEffect(() => {
setMyValue(value);
}, [closePopUp]);
}, [disabled]);
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<input
value={myValue}
onFocus={() => {
setDisableCopyPaste(true);
}}
onBlur={() => {
setDisableCopyPaste(false);
}}
className={
editNode
? " input-edit-node "
: " input-primary " + (disabled ? " input-disable" : "")
}
placeholder={"Type something..."}
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>
<button
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.TEXT}
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={myValue}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
<div className="flex w-full items-center">
<Input
value={value}
disabled={disabled}
className={editNode ? "input-edit-node" : ""}
placeholder={"Type something..."}
onChange={(e) => {
onChange(e.target.value);
}}
/>
<div>
<GenericModal
type={TypeModal.TEXT}
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={value}
setValue={(t: string) => {
onChange(t);
}}
>
{!editNode && (
@ -76,7 +48,7 @@ export default function TextAreaComponent({
}
/>
)}
</button>
</GenericModal>
</div>
</div>
);

View file

@ -25,6 +25,7 @@ export default function ToggleShadComponent({
scaleX = 1;
scaleY = 1;
}
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch

View file

@ -56,4 +56,4 @@ const AccordionContent = React.forwardRef<
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };

View file

@ -77,9 +77,9 @@ CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
};

View file

@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full gap-3 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
"noundo nocopy fixed z-50 flex w-full flex-col gap-3 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
className
)}
{...props}
@ -116,10 +116,10 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
};

View file

@ -182,18 +182,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
DropdownMenuTrigger,
};

View file

@ -9,10 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
className={cn("nopan nodrag noundo nocopy input-primary", className)}
ref={ref}
{...props}
/>

View file

@ -218,19 +218,19 @@ MenubarShortcut.displayname = "MenubarShortcut";
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarPortal,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
MenubarTrigger,
};

View file

@ -57,7 +57,7 @@ export default function RenameLabel(props) {
ref={inputRef}
onInput={resizeInput}
className={cn(
"rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
"nopan nodrag noundo nocopy rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
props.className
)}
onBlur={() => {

View file

@ -103,11 +103,11 @@ TableCaption.displayName = "TableCaption";
export {
Table,
TableHeader,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
TableCell,
TableCaption,
};

View file

@ -13,7 +13,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
"inline-flex h-10 items-center justify-center rounded-md p-1 text-muted-foreground",
className
)}
{...props}
@ -51,4 +51,4 @@ const TabsContent = React.forwardRef<
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };
export { Tabs, TabsContent, TabsList, TabsTrigger };

View file

@ -8,10 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
className={cn("nopan nodrag noundo nocopy textarea-primary", className)}
ref={ref}
{...props}
/>

View file

@ -28,4 +28,4 @@ const TooltipContent = React.forwardRef<
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };

View file

@ -42,8 +42,6 @@ const TabsContextInitialValue: TabsContextType = {
uploadFlow: () => {},
hardReset: () => {},
saveFlow: async (flow: FlowType) => {},
disableCopyPaste: false,
setDisableCopyPaste: (state: boolean) => {},
lastCopiedSelection: null,
setLastCopiedSelection: (selection: any) => {},
tabsState: {},
@ -585,16 +583,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
}
const [disableCopyPaste, setDisableCopyPaste] = useState(false);
return (
<TabsContext.Provider
value={{
saveFlow,
lastCopiedSelection,
setLastCopiedSelection,
disableCopyPaste,
setDisableCopyPaste,
hardReset,
tabId,
setTabId,

View file

@ -71,18 +71,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
// Clear the interval if successful.
clearInterval(intervalId);
} catch (error) {
retryCount++;
// On error, double the delay for the next attempt up to a maximum.
delay = Math.min(30000, delay * 2);
// Log errors but don't do anything else - the function will try again on the next interval.
console.error(error);
// Clear the old interval and start a new one with the new delay.
if (retryCount <= maxRetryCount) {
clearInterval(intervalId);
intervalId = setInterval(getTypes, delay);
} else {
console.error("Max retry attempts reached. Stopping retries.");
}
console.error("An error has occurred while fetching types.");
}
}

View file

@ -13,6 +13,7 @@ import {
undoRedoContextType,
} from "../types/typesContext";
import { TabsContext } from "./tabsContext";
import { isWrappedWithClass } from "../utils/utils";
const initialValue = {
undo: () => {},
@ -137,17 +138,21 @@ export function UndoRedoProvider({ children }) {
}
const keyDownHandler = (event: KeyboardEvent) => {
if (
event.key === "z" &&
(event.ctrlKey || event.metaKey) &&
event.shiftKey
) {
redo();
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
event.preventDefault(); // prevent the default action
redo();
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
undo();
if (!isWrappedWithClass(event, "noundo")) {
if (
event.key === "z" &&
(event.ctrlKey || event.metaKey) &&
event.shiftKey
) {
event.preventDefault();
redo();
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
event.preventDefault(); // prevent the default action
redo();
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
undo();
}
}
};

View file

@ -0,0 +1,58 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef } from "react";
import { alertContext } from "../../contexts/alertContext";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
baseURL: "",
});
function ApiInterceptor() {
const retryCounts = useRef([]);
const { setErrorData } = useContext(alertContext);
useEffect(() => {
const interceptor = api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
let retryCount = 0;
while (retryCount < 4) {
await sleep(5000); // Sleep for 5 seconds
retryCount++;
try {
const response = await axios.request(error.config);
return response;
} catch (error) {
if (retryCount === 3) {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
"Endpoint: " + error.config.url,
],
});
return Promise.reject(error);
}
}
}
}
);
return () => {
// Clean up the interceptor when the component unmounts
api.interceptors.response.eject(interceptor);
};
}, [retryCounts]);
return null;
}
// Function to sleep for a given duration in milliseconds
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export { ApiInterceptor, api };

View file

@ -1,5 +1,6 @@
import axios, { AxiosResponse } from "axios";
import { AxiosResponse } from "axios";
import { ReactFlowJsonObject } from "reactflow";
import { api } from "../../controllers/API/api";
import { APIObjectType, sendAllProps } from "../../types/api/index";
import { FlowStyleType, FlowType } from "../../types/flow";
import {
@ -17,16 +18,14 @@ import {
* @returns {Promise<AxiosResponse<APIObjectType>>} A promise that resolves to an AxiosResponse containing all the objects.
*/
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
return await axios.get(`/api/v1/all`);
return await api.get(`/api/v1/all`);
}
const GITHUB_API_URL = "https://api.github.com";
export async function getRepoStars(owner, repo) {
try {
const response = await axios.get(
`${GITHUB_API_URL}/repos/${owner}/${repo}`
);
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
return response.data.stargazers_count;
} catch (error) {
console.error("Error fetching repository data:", error);
@ -41,13 +40,13 @@ export async function getRepoStars(owner, repo) {
* @returns {AxiosResponse<any>} The API response.
*/
export async function sendAll(data: sendAllProps) {
return await axios.post(`/api/v1/predict`, data);
return await api.post(`/api/v1/predict`, data);
}
export async function postValidateCode(
code: string
): Promise<AxiosResponse<errorsTypeAPI>> {
return await axios.post("/api/v1/validate/code", { code });
return await api.post("/api/v1/validate/code", { code });
}
/**
@ -62,7 +61,7 @@ export async function postValidatePrompt(
template: string,
frontend_node: APIClassType
): Promise<AxiosResponse<PromptTypeAPI>> {
return await axios.post("/api/v1/validate/prompt", {
return await api.post("/api/v1/validate/prompt", {
name: name,
template: template,
frontend_node: frontend_node,
@ -76,15 +75,15 @@ export async function postValidatePrompt(
*/
export async function getExamples(): Promise<FlowType[]> {
const url =
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=fix_examples";
const response = await axios.get(url);
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=main";
const response = await api.get(url);
const jsonFiles = response.data.filter((file: any) => {
return file.name.endsWith(".json");
});
const contentsPromises = jsonFiles.map(async (file: any) => {
const contentResponse = await axios.get(file.download_url);
const contentResponse = await api.get(file.download_url);
return contentResponse.data;
});
@ -106,11 +105,12 @@ export async function saveFlowToDatabase(newFlow: {
style?: FlowStyleType;
}): Promise<FlowType> {
try {
const response = await axios.post("/api/v1/flows/", {
const response = await api.post("/api/v1/flows/", {
name: newFlow.name,
data: newFlow.data,
description: newFlow.description,
});
if (response.status !== 201) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -131,7 +131,7 @@ export async function updateFlowInDatabase(
updatedFlow: FlowType
): Promise<FlowType> {
try {
const response = await axios.patch(`/api/v1/flows/${updatedFlow.id}`, {
const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
name: updatedFlow.name,
data: updatedFlow.data,
description: updatedFlow.description,
@ -155,7 +155,7 @@ export async function updateFlowInDatabase(
*/
export async function readFlowsFromDatabase() {
try {
const response = await axios.get("/api/v1/flows/");
const response = await api.get("/api/v1/flows/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -168,7 +168,7 @@ export async function readFlowsFromDatabase() {
export async function downloadFlowsFromDatabase() {
try {
const response = await axios.get("/api/v1/flows/download/");
const response = await api.get("/api/v1/flows/download/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -181,7 +181,7 @@ export async function downloadFlowsFromDatabase() {
export async function uploadFlowsToDatabase(flows) {
try {
const response = await axios.post(`/api/v1/flows/upload/`, flows);
const response = await api.post(`/api/v1/flows/upload/`, flows);
if (response.status !== 201) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -202,7 +202,7 @@ export async function uploadFlowsToDatabase(flows) {
*/
export async function deleteFlowFromDatabase(flowId: string) {
try {
const response = await axios.delete(`/api/v1/flows/${flowId}`);
const response = await api.delete(`/api/v1/flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -222,7 +222,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
*/
export async function getFlowFromDatabase(flowId: number) {
try {
const response = await axios.get(`/api/v1/flows/${flowId}`);
const response = await api.get(`/api/v1/flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -241,7 +241,7 @@ export async function getFlowFromDatabase(flowId: number) {
*/
export async function getFlowStylesFromDatabase() {
try {
const response = await axios.get("/api/v1/flow_styles/");
const response = await api.get("/api/v1/flow_styles/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -261,7 +261,7 @@ export async function getFlowStylesFromDatabase() {
*/
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
try {
const response = await axios.post("/api/v1/flow_styles/", flowStyle, {
const response = await api.post("/api/v1/flow_styles/", flowStyle, {
headers: {
accept: "application/json",
"Content-Type": "application/json",
@ -284,7 +284,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
*/
export async function getVersion() {
const respnose = await axios.get("/api/v1/version");
const respnose = await api.get("/api/v1/version");
return respnose.data;
}
@ -294,7 +294,7 @@ export async function getVersion() {
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the health status.
*/
export async function getHealth() {
return await axios.get("/health"); // Health is the only endpoint that doesn't require /api/v1
return await api.get("/health"); // Health is the only endpoint that doesn't require /api/v1
}
/**
@ -306,7 +306,7 @@ export async function getHealth() {
export async function getBuildStatus(
flowId: string
): Promise<BuildStatusTypeAPI> {
return await axios.get(`/api/v1/build/${flowId}/status`);
return await api.get(`/api/v1/build/${flowId}/status`);
}
//docs for postbuildinit
@ -319,7 +319,7 @@ export async function getBuildStatus(
export async function postBuildInit(
flow: FlowType
): Promise<AxiosResponse<InitTypeAPI>> {
return await axios.post(`/api/v1/build/init/${flow.id}`, flow);
return await api.post(`/api/v1/build/init/${flow.id}`, flow);
}
// fetch(`/upload/${id}`, {
@ -337,5 +337,5 @@ export async function uploadFile(
): Promise<AxiosResponse<UploadFileTypeAPI>> {
const formData = new FormData();
formData.append("file", file);
return await axios.post(`/api/v1/upload/${id}`, formData);
return await api.post(`/api/v1/upload/${id}`, formData);
}

View file

@ -38,9 +38,10 @@
--info-background: #f0f4fd;
--info-foreground: #141653;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
--chat-bot-icon: #afe6ef;
--chat-user-icon: #aface9;
@ -52,6 +53,7 @@
--chat-trigger-disabled: #b4c3da;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue:#2563eb;
--connection: #555;
@ -104,6 +106,7 @@
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923d2;
@ -112,6 +115,7 @@
--chat-trigger-disabled: #2d3b54;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
@ -145,6 +149,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
pre {
font-family: inherit;
}
/* The style below sets the cursor property of the element with the class .react-flow__pane to the default cursor.
The cursor: default; property value restores the browser's default cursor style for the targeted element. By applying this style, the element will no longer have a custom cursor appearance such as "grab" or any other custom cursor defined elsewhere in the application. Instead, it will revert to the default cursor style determined by the browser, typically an arrow-shaped cursor. */
@ -236,15 +243,17 @@ The cursor: default; property value restores the browser's default cursor style
.button-div-style {
@apply gap-2 flex
}
.input-primary:focus{
@apply focus:placeholder-transparent focus:ring-ring focus:border-ring
.input-primary{
@apply disabled:cursor-not-allowed disabled:opacity-50 focus:placeholder-transparent focus:ring-ring focus:border-ring bg-background block text-left border-border form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm w-full truncate
}
.input-primary {
@apply bg-background block border-border form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm truncate w-full;
/* The same as input-primary but no-truncate */
.textarea-primary{
@apply disabled:cursor-not-allowed disabled:opacity-50 focus:placeholder-transparent focus:ring-ring focus:border-ring bg-background block text-left border-border form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm w-full
}
.input-edit-node{
@apply input-primary border-border placeholder:text-center pt-0.5 pb-0.5 text-center
@apply input-primary border-border pt-0.5 pb-0.5 text-left w-full
}
.input-search{
@apply input-primary pr-7 mx-2
@ -547,7 +556,7 @@ The cursor: default; property value restores the browser's default cursor style
@apply z-10 mt-1 max-h-60 overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm
}
.dropdown-component-true-options {
@apply dropdown-component-options w-[215px]
@apply dropdown-component-options lg:w-[32%]
}
.dropdown-component-false-options {
@apply dropdown-component-options w-full
@ -932,7 +941,7 @@ The cursor: default; property value restores the browser's default cursor style
}
.api-modal-tabs {
@apply w-full h-full overflow-hidden text-center bg-muted rounded-md border
@apply lg:w-full h-full flex flex-col overflow-hidden text-center bg-muted rounded-md border sm:w-[75vw]
}
.api-modal-tablist-div {
@apply flex items-center justify-between px-2

View file

@ -4,6 +4,7 @@ import App from "./App";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";
import { ApiInterceptor } from "./controllers/API/api";
import "./index.css";
const root = ReactDOM.createRoot(
@ -13,6 +14,7 @@ root.render(
<ContextWrapper>
<BrowserRouter>
<App />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);

View file

@ -2,734 +2,285 @@ 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 { Check, Clipboard } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import AccordionComponent from "../../components/AccordionComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
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 InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
import PromptAreaComponent from "../../components/promptComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { FlowType, NodeDataType } from "../../types/flow/index";
import { buildTweaks } from "../../utils/reactflowUtils";
import {
classNames,
getCurlCode,
getPythonApiCode,
getPythonCode,
} from "../../utils/utils";
export default function ApiModal({ flow }: { flow: FlowType }): JSX.Element {
const [open, setOpen] = useState(true);
const { dark } = useContext(darkContext);
const { closePopUp, closeEdit, setCloseEdit } = useContext(PopUpContext);
const [activeTab, setActiveTab] = useState("0");
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [enabled, setEnabled] = useState(null);
const [openAccordion, setOpenAccordion] = useState([]);
const tweak = useRef([]);
const tweaksList = useRef([]);
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
import BaseModal from "../baseModal";
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const tweaksCode = buildTweaks(flow);
const tabs = [
const ApiModal = forwardRef(
(
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
code: curl_code,
flow,
children,
}: {
flow: FlowType;
children: ReactNode;
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
},
];
ref
) => {
const [open, setOpen] = useState(false);
const [activeTab, setActiveTab] = useState("0");
const tweak = useRef([]);
const tweaksList = useRef([]);
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const tweaksCode = buildTweaks(flow);
const [tabs, setTabs] = useState([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
]);
useEffect(() => {
if (closeEdit !== "") {
tweak.current = getTweak;
if (tweak.current.length > 0) {
setActiveTab("3");
openAccordions();
} else {
startTweaks();
}
} else {
if (tweak?.current) {
startTweaks();
}
}
}, [closeEdit]);
useEffect(() => {
filterNodes();
}, []);
if (Object.keys(tweaksCode).length > 0) {
tabs.push({
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
});
}
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setCloseEdit("");
function startState() {
tweak.current = [];
setTweak([]);
closePopUp();
}
}
function startTweaks() {
const t = buildTweaks(flow);
tweak?.current?.push(t);
}
function filterNodes() {
let arrNodesWithValues = [];
flow["data"]["nodes"].forEach((t) => {
Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type === "bool" ||
t.data.node.template[n].type === "float" ||
t.data.node.template[n].type === "code" ||
t.data.node.template[n].type === "prompt" ||
t.data.node.template[n].type === "file" ||
t.data.node.template[n].type === "int")
)
.map((n, i) => {
arrNodesWithValues.push(t["id"]);
});
});
tweaksList.current = arrNodesWithValues.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
// DIFICULT IN FIND WHERE TW,CHANGES AND TEMPLATE ARE USED
function buildTweakObject(tw, changes, template): void {
if (template.type === "float") {
changes = parseFloat(changes);
}
if (template.type === "int") {
changes = parseInt(changes);
}
if (template.list === true && Array.isArray(changes)) {
changes = changes?.filter((x) => x !== "");
tweaksList.current = [];
}
const existingTweak = tweak.current.find((element) =>
element.hasOwnProperty(tw)
);
if (existingTweak) {
existingTweak[tw][template["name"]] = changes;
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 !== "";
});
}
});
useEffect(() => {
if (flow["data"]["nodes"].length == 0) {
startState();
} else {
tweak.current = [];
const t = buildTweaks(flow);
tweak.current.push(t);
}
} else {
const newTweak = {
[tw]: {
[template["name"]]: changes,
},
};
tweak.current.push(newTweak);
}
const pythonApiCode = getPythonApiCode(flow, tweak.current);
const curl_code = getCurlCode(flow, tweak.current);
const pythonCode = getPythonCode(flow, tweak.current);
filterNodes();
tabs[0].code = curl_code;
tabs[1].code = pythonApiCode;
tabs[2].code = pythonCode;
setTweak(tweak.current);
}
function buildContent(value: string): JSX.Element {
const htmlContent = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
</div>
);
return htmlContent;
}
// DIFICULT FINDIND WHERE TEMPLATE IS USED
function getValue(value: string, node: NodeDataType, template): string {
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];
}
});
}
});
if (Object.keys(tweaksCode).length > 0) {
setActiveTab("0");
setTabs([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
language: "py",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
},
{
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
]);
} else {
setTabs([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
]);
}
} else {
return value ?? "";
}
return returnValue;
}
}, [flow["data"]["nodes"], open]);
function openAccordions(): void {
let accordionsToOpen = [];
tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen);
}
function filterNodes() {
let arrNodesWithValues = [];
flow["data"]["nodes"].forEach((t) => {
Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type === "bool" ||
t.data.node.template[n].type === "float" ||
t.data.node.template[n].type === "code" ||
t.data.node.template[n].type === "prompt" ||
t.data.node.template[n].type === "file" ||
t.data.node.template[n].type === "int")
)
.map((n, i) => {
arrNodesWithValues.push(t["id"]);
});
});
});
}
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="h-[80vh] md:max-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Code</span>
<IconComponent
name="Code2"
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{EXPORT_CODE_DIALOG}</DialogDescription>
</DialogHeader>
tweaksList.current = arrNodesWithValues.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
function buildTweakObject(tw, changes, template) {
if (template.type === "float") {
changes = parseFloat(changes);
}
if (template.type === "int") {
changes = parseInt(changes);
}
if (template.list === true && Array.isArray(changes)) {
changes = changes?.filter((x) => x !== "");
}
<Tabs
value={activeTab}
className="api-modal-tabs"
onValueChange={(value) => {
setActiveTab(value);
if (value === "3") {
openAccordions();
const existingTweak = tweak.current.find((element) =>
element.hasOwnProperty(tw)
);
if (existingTweak) {
existingTweak[tw][template["name"]] = changes;
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 !== "";
});
}
}}
>
<div className="api-modal-tablist-div">
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger key={index} value={index.toString()}>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{Number(activeTab) < 3 && (
<div className="float-right">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
>
{isCopied ? <Check size={18} /> : <Clipboard size={15} />}
{isCopied ? "Copied!" : "Copy code"}
</button>
</div>
)}
</div>
});
}
} else {
const newTweak = {
[tw]: {
[template["name"]]: changes,
},
};
tweak.current.push(newTweak);
}
{tabs.map((tab, index) => (
<TabsContent
value={index.toString()}
className="api-modal-tabs-content"
key={index} // Remember to add a unique key prop
>
{index < 3 ? (
<SyntaxHighlighter
className="h-[60vh] w-full overflow-auto custom-scroll"
language={tab.mode}
style={oneDark}
>
{tab.code}
</SyntaxHighlighter>
) : index === 3 ? (
<>
<div className="api-modal-according-display">
<div
className={classNames(
"h-[60vh] w-full rounded-lg bg-muted",
1 == 1
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
{flow["data"]["nodes"].map((t: any, index) => (
<div className="px-3" key={index}>
{tweaksList.current.includes(t["data"]["id"]) && (
<AccordionComponent
trigger={t["data"]["id"]}
keyValue={t["data"]["id"]}
open={openAccordion}
>
<div className="api-modal-table-arrangement">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="h-10 border-input text-xs font-medium text-ring">
<TableRow className="dark:border-b-muted">
<TableHead className="h-7 text-center">
PARAM
</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type ===
"str" ||
t.data.node.template[n].type ===
"bool" ||
t.data.node.template[n].type ===
"float" ||
t.data.node.template[n].type ===
"code" ||
t.data.node.template[n].type ===
"prompt" ||
t.data.node.template[n].type ===
"file" ||
t.data.node.template[n].type ===
"int")
)
.map((n, i) => {
return (
<TableRow
key={i}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-foreground">
{n}
</TableCell>
<TableCell className="p-0 text-xs text-foreground">
<div className="m-auto w-[250px]">
{t.data.node.template[n]
.type === "str" &&
!t.data.node.template[n]
.options ? (
<div className="mx-auto">
{t.data.node.template[n]
.list ? (
<InputListComponent
editNode={true}
disabled={false}
value={
!t.data.node.template[
n
].value ||
t.data.node.template[
n
].value === ""
? [""]
: t.data.node
.template[n]
.value
}
onChange={(k) => {}}
onAddInput={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
) : t.data.node.template[n]
.multiline ? (
<ShadTooltip
content={buildContent(
t.data.node.template[
n
].value
)}
>
<div>
<TextAreaComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node
.template[n]
.value,
t.data,
t.data.node
.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : (
<InputComponent
editNode={true}
disabled={false}
password={
t.data.node.template[
n
].password ?? false
}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
)}
</div>
) : t.data.node.template[n]
.type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
enabled={
t.data.node.template[n]
.value
}
setEnabled={(e) => {
t.data.node.template[
n
].value = e;
setEnabled(e);
buildTweakObject(
t["data"]["id"],
e,
t.data.node.template[
n
]
);
}}
size="small"
disabled={false}
/>
</div>
) : t.data.node.template[n]
.type === "file" ? (
<ShadTooltip
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={false}
value={
t.data.node.template[
n
].value ?? ""
}
onChange={(
k: any
) => {}}
fileTypes={
t.data.node.template[
n
].fileTypes
}
suffixes={
t.data.node.template[
n
].suffixes
}
onFileChange={(
k: any
) => {}}
></InputFileComponent>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
/>
</div>
) : t.data.node.template[n]
.type === "str" &&
t.data.node.template[n]
.options ? (
<div className="mx-auto">
<Dropdown
editNode={true}
apiModal={true}
options={
t.data.node.template[n]
.options
}
onSelect={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
></Dropdown>
</div>
) : t.data.node.template[n]
.type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
/>
</div>
) : t.data.node.template[n]
.type === "prompt" ? (
<ShadTooltip
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<PromptAreaComponent
editNode={true}
disabled={false}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "code" ? (
<ShadTooltip
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<CodeAreaComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "Any" ? (
"-"
) : (
<div className="hidden"></div>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</AccordionComponent>
)}
const pythonApiCode = getPythonApiCode(flow, tweak.current);
const curl_code = getCurlCode(flow, tweak.current);
const pythonCode = getPythonCode(flow, tweak.current);
{tweaksList.current.length === 0 && (
<>
<div className="pt-3">
No tweaks are available for this flow.
</div>
</>
)}
</div>
))}
</div>
</div>
</>
) : null}
</TabsContent>
))}
</Tabs>
</DialogContent>
</Dialog>
);
}
tabs[0].code = curl_code;
tabs[1].code = pythonApiCode;
tabs[2].code = pythonCode;
setTweak(tweak.current);
}
function buildContent(value) {
const htmlContent = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
</div>
);
return htmlContent;
}
function getValue(value, node, template) {
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>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">Code</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;

View file

@ -1,4 +1,5 @@
import { useContext, useRef, useState } from "react";
import { cloneDeep } from "lodash";
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
@ -12,15 +13,6 @@ import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import {
Table,
TableBody,
@ -30,319 +22,304 @@ import {
TableRow,
} from "../../components/ui/table";
import { limitScrollFieldsModal } from "../../constants/constants";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { editNodeToggleType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import { classNames } from "../../utils/utils";
import BaseModal from "../baseModal";
export default function EditNodeModal({
data,
}: {
data: NodeDataType;
}): JSX.Element {
const [open, setOpen] = useState(true);
const [nodeLength, setNodeLength] = useState(
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].show &&
(data.node.template[t].type === "str" ||
data.node.template[t].type === "bool" ||
data.node.template[t].type === "float" ||
data.node.template[t].type === "code" ||
data.node.template[t].type === "prompt" ||
data.node.template[t].type === "file" ||
data.node.template[t].type === "int")
).length
);
const [nodeValue, setNodeValue] = useState(null);
const { closePopUp } = useContext(PopUpContext);
const { types } = useContext(typesContext);
const ref = useRef();
const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
const EditNodeModal = forwardRef(
(
{
data,
setData,
nodeLength,
children,
}: {
data: NodeDataType;
setData: (data: NodeDataType) => void;
nodeLength: number;
children: ReactNode;
},
ref
) => {
const [modalOpen, setModalOpen] = useState(false);
const [myData, setMyData] = useState(data);
const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === data.id) ??
false;
if (nodeLength == 0) {
closePopUp();
}
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === data.id) ??
false;
function setModalOpen(x: boolean): void {
setOpen(x);
if (x === false) {
closePopUp();
function changeAdvanced(n) {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node.template[n].advanced = !newData.node.template[n].advanced;
return newData;
});
}
}
function changeAdvanced(node: editNodeToggleType): void {
Object.keys(data.node.template).map((n, i) => {
if (n === node.name) {
data.node.template[n].advanced = !data.node.template[n].advanced;
}
return n;
});
setNodeValue(!nodeValue);
}
const handleOnNewValue = (newValue: any, name) => {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node.template[name].value = newValue;
return newData;
});
};
const handleOnNewValue = (
newValue: string | string[] | boolean,
name: string
): void => {
data.node.template[name].value = newValue;
// Set state to pending
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
};
useEffect(() => {
setMyData(data); // reset data to what it is on node when opening modal
}, [modalOpen]);
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger asChild></DialogTrigger>
<DialogContent className="sm:max-w-[600px] lg:max-w-[700px]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">{data.type}</span>
<Badge variant="secondary">ID: {data.id}</Badge>
</DialogTitle>
<DialogDescription asChild>
<div>
{data.node?.description}
<div className="flex pt-3">
<IconComponent
name="Variable"
className="edit-node-modal-variable "
/>
<span className="edit-node-modal-span">Parameters</span>
</div>
</div>
</DialogDescription>
</DialogHeader>
return (
<BaseModal size="large-h-full" open={modalOpen} setOpen={setModalOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={myData.node?.description}>
<span className="pr-2">{myData.type}</span>
<Badge variant="secondary">ID: {myData.id}</Badge>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex pb-2">
<IconComponent
name="Variable"
className="edit-node-modal-variable "
/>
<span className="edit-node-modal-span">Parameters</span>
</div>
<div className="edit-node-modal-arrangement">
<div
className={classNames(
"edit-node-modal-box",
nodeLength > limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
{nodeLength > 0 && (
<div className="edit-node-modal-table">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="edit-node-modal-table-header">
<TableRow className="">
<TableHead className="h-7 text-center">PARAM</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
<TableHead className="h-7 text-center">SHOW</TableHead>
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(data.node.template)
.filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].show &&
(data.node.template[t].type === "str" ||
data.node.template[t].type === "bool" ||
data.node.template[t].type === "float" ||
data.node.template[t].type === "code" ||
data.node.template[t].type === "prompt" ||
data.node.template[t].type === "file" ||
data.node.template[t].type === "int")
)
.map((n, i) => (
<TableRow key={i} className="h-10">
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
{data.node.template[n].name
? data.node.template[n].name
: data.node.template[n].display_name}
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{data.node.template[n].type === "str" &&
!data.node.template[n].options ? (
<div className="mx-auto">
{data.node.template[n].list ? (
<InputListComponent
editNode={true}
<div className="edit-node-modal-arrangement">
<div
className={classNames(
"edit-node-modal-box",
nodeLength > limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
{nodeLength > 0 && (
<div className="edit-node-modal-table">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="edit-node-modal-table-header">
<TableRow className="">
<TableHead className="h-7 text-center">PARAM</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
<TableHead className="h-7 text-center">SHOW</TableHead>
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(myData.node.template)
.filter(
(t) =>
t.charAt(0) !== "_" &&
myData.node.template[t].show &&
(myData.node.template[t].type === "str" ||
myData.node.template[t].type === "bool" ||
myData.node.template[t].type === "float" ||
myData.node.template[t].type === "code" ||
myData.node.template[t].type === "prompt" ||
myData.node.template[t].type === "file" ||
myData.node.template[t].type === "int")
)
.map((n, i) => (
<TableRow key={i} className="h-10">
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
{myData.node.template[n].name
? myData.node.template[n].name
: myData.node.template[n].display_name}
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{myData.node.template[n].type === "str" &&
!myData.node.template[n].options ? (
<div className="mx-auto">
{myData.node.template[n].list ? (
<InputListComponent
editNode={true}
disabled={disabled}
value={
!myData.node.template[n].value ||
myData.node.template[n].value === ""
? [""]
: myData.node.template[n].value
}
onChange={(t: string[]) => {
handleOnNewValue(t, n);
}}
/>
) : myData.node.template[n].multiline ? (
<TextAreaComponent
disabled={disabled}
editNode={true}
value={
myData.node.template[n].value ?? ""
}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
) : (
<InputComponent
editNode={true}
disabled={disabled}
password={
myData.node.template[n].password ??
false
}
value={
myData.node.template[n].value ?? ""
}
onChange={(t) => {
handleOnNewValue(t, n);
}}
/>
)}
</div>
) : myData.node.template[n].type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
disabled={disabled}
value={
!data.node.template[n].value ||
data.node.template[n].value === ""
? [""]
: data.node.template[n].value
}
onChange={(t: string[]) => {
console.log(t);
enabled={myData.node.template[n].value}
setEnabled={(t) => {
handleOnNewValue(t, n);
}}
size="small"
/>
) : data.node.template[n].multiline ? (
<TextAreaComponent
</div>
) : myData.node.template[n].type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
) : (
<InputComponent
editNode={true}
disabled={disabled}
password={
data.node.template[n].password ?? false
}
value={data.node.template[n].value ?? ""}
value={myData.node.template[n].value ?? ""}
onChange={(t) => {
handleOnNewValue(t, n);
}}
/>
)}
</div>
) : data.node.template[n].type === "bool" ? (
<div className="ml-auto">
{" "}
oi
</div>
) : myData.node.template[n].type === "str" &&
myData.node.template[n].options ? (
<div className="mx-auto">
<Dropdown
numberOfOptions={nodeLength}
editNode={true}
options={myData.node.template[n].options}
onSelect={(t) => handleOnNewValue(t, n)}
value={
myData.node.template[n].value ??
"Choose an option"
}
></Dropdown>
</div>
) : myData.node.template[n].type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}
onChange={(t) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : myData.node.template[n].type === "file" ? (
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={disabled}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
fileTypes={
myData.node.template[n].fileTypes
}
suffixes={myData.node.template[n].suffixes}
onFileChange={(t: string) => {
handleOnNewValue(t, n);
}}
></InputFileComponent>
</div>
) : myData.node.template[n].type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
field_name={n}
editNode={true}
disabled={disabled}
nodeClass={myData.node}
setNodeClass={(nodeClass) => {
myData.node = nodeClass;
}}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : myData.node.template[n].type === "code" ? (
<div className="mx-auto">
<CodeAreaComponent
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : myData.node.template[n].type === "Any" ? (
"-"
) : (
<div className="hidden"></div>
)}
</TableCell>
<TableCell className="p-0 text-right">
<div className="items-center text-center">
<ToggleShadComponent
enabled={!myData.node.template[n].advanced}
setEnabled={(e) => changeAdvanced(n)}
disabled={disabled}
enabled={data.node.template[n].value}
setEnabled={(t) => {
handleOnNewValue(t, n);
}}
size="small"
/>
</div>
) : data.node.template[n].type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t) => {
data.node.template[n].value = t;
}}
/>
</div>
) : data.node.template[n].type === "str" &&
data.node.template[n].options ? (
<div className="mx-auto">
<Dropdown
numberOfOptions={nodeLength}
editNode={true}
options={data.node.template[n].options}
onSelect={(t) => handleOnNewValue(t, n)}
value={
data.node.template[n].value ??
"Choose an option"
}
></Dropdown>
</div>
) : data.node.template[n].type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : data.node.template[n].type === "file" ? (
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={disabled}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
fileTypes={data.node.template[n].fileTypes}
suffixes={data.node.template[n].suffixes}
onFileChange={(t: string) => {
handleOnNewValue(t, n);
}}
></InputFileComponent>
</div>
) : data.node.template[n].type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
field_name={n}
editNode={true}
disabled={disabled}
nodeClass={data.node}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : data.node.template[n].type === "code" ? (
<div className="mx-auto">
<CodeAreaComponent
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
}}
/>
</div>
) : data.node.template[n].type === "Any" ? (
"-"
) : (
<div className="hidden"></div>
)}
</TableCell>
<TableCell className="p-0 text-right">
<div className="items-center text-center">
<ToggleShadComponent
enabled={!data.node.template[n].advanced}
setEnabled={(e) =>
changeAdvanced(data.node.template[n])
}
disabled={disabled}
size="small"
/>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
</div>
</div>
</BaseModal.Content>
<DialogFooter>
<BaseModal.Footer>
<Button
className="mt-3"
onClick={() => {
setData(cloneDeep(myData)); //saves data with actual state of modal
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
setModalOpen(false);
}}
type="submit"
>
Save Changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
</BaseModal.Footer>
</BaseModal>
);
}
);
export default EditNodeModal;

View file

@ -1,4 +1,4 @@
import { ReactNode, useContext } from "react";
import { ReactNode } from "react";
import React from "react";
import {
@ -9,12 +9,19 @@ import {
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { PopUpContext } from "../../contexts/popUpContext";
import { ContentProps, HeaderProps, headerConstType } from "../../types/components";
import { headerConstType } from "../../types/components";
type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string };
type FooterProps = { children: ReactNode };
type TriggerProps = { children: ReactNode };
const Content: React.FC<ContentProps> = ({ children }) => {
return <div className="h-full w-full">{children}</div>;
};
const Trigger: React.FC<ContentProps> = ({ children }) => {
return <>{children}</>;
};
const Header: React.FC<{ children: ReactNode; description: string }> = ({
children,
@ -27,36 +34,82 @@ const Header: React.FC<{ children: ReactNode; description: string }> = ({
</DialogHeader>
);
};
interface BaseModalProps {
children: [React.ReactElement<ContentProps>, React.ReactElement<HeaderProps>];
open: boolean;
setOpen: (open: boolean) => void;
}
function BaseModal({ open, setOpen, children }: BaseModalProps): JSX.Element {
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
function setModalOpen(x: boolean): void {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("editcode");
closePopUp();
}, 300);
}
}
const Footer: React.FC<{ children: ReactNode }> = ({ children }) => {
return <>{children}</>;
};
interface BaseModalProps {
children: [
React.ReactElement<ContentProps>,
React.ReactElement<HeaderProps>,
React.ReactElement<TriggerProps>?,
React.ReactElement<FooterProps>?
];
open?: boolean;
setOpen?: (open: boolean) => void;
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
}
function BaseModal({
open,
setOpen,
children,
size = "large",
}: BaseModalProps) {
const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header
);
const triggerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Trigger
);
const ContentChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Content
);
const ContentFooter = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Footer
);
let minWidth: string;
let height: string;
switch (size) {
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[25vh]";
break;
case "small":
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "large":
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
case "large-h-full":
minWidth = "min-w-[80vw]";
break;
default:
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
}
//UPDATE COLORS AND STYLE CLASSSES
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger>
<DialogContent className="min-w-[80vw]">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="w-full" hidden={triggerChild ? false : true}>
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}>
{headerChild}
<div className="mt-2 flex h-[80vh] w-full ">{ContentChild}</div>
<div className={`mt-2 flex flex-col ${height} w-full `}>
{ContentChild}
</div>
<div className="flex flex-row-reverse">{ContentFooter}</div>
</DialogContent>
</Dialog>
);
@ -64,4 +117,6 @@ function BaseModal({ open, setOpen, children }: BaseModalProps): JSX.Element {
BaseModal.Content = Content;
BaseModal.Header = Header;
BaseModal.Trigger = Trigger;
BaseModal.Footer = Footer;
export default BaseModal;

View file

@ -1,17 +1,15 @@
import { DialogTitle } from "@radix-ui/react-dialog";
import "ace-builds/src-noconflict/ace";
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 { useContext, useState } from "react";
import { ReactNode, useContext, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { postValidateCode } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import BaseModal from "../baseModal";
@ -22,21 +20,13 @@ export default function CodeAreaModal({
setValue,
nodeClass,
setNodeClass,
children,
}: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value);
const { dark } = useContext(darkContext);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
function setModalOpen(x: boolean): void {
if (x === false) {
setCloseEdit("codearea");
closePopUp();
}
}
// Check for custom code errors
function handleClick(): void {
function handleClick() {
postValidateCode(code)
.then((apiReturn) => {
if (apiReturn.data) {
@ -47,7 +37,7 @@ export default function CodeAreaModal({
title: "Code is ready to run",
});
setValue(code);
setModalOpen(false);
setOpen(false);
} else {
if (funcErrors.length !== 0) {
setErrorData({
@ -75,17 +65,18 @@ export default function CodeAreaModal({
});
}
const [open, setOpen] = useState(false);
return (
<BaseModal open={true} setOpen={setModalOpen}>
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
<DialogTitle className="flex items-center">
<span className="pr-2">Edit Code</span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
<span className="pr-2">Edit Code</span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full w-full flex-col transition-all">

View file

@ -1,60 +1,34 @@
import { useContext, useRef, useState } from "react";
import { ReactNode, forwardRef, useContext, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
export default function ExportModal(): JSX.Element {
const [open, setOpen] = useState(true);
const { closePopUp } = useContext(PopUpContext);
const ref = useRef();
const { setErrorData } = useContext(alertContext);
const ExportModal = forwardRef((props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, updateFlow, downloadFlow, saveFlow } =
useContext(TabsContext);
const [isMaxLength, setIsMaxLength] = useState(false);
function setModalOpen(x: boolean): void {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
const [checked, setChecked] = useState(false);
const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
const [open, setOpen] = useState(false);
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger asChild></DialogTrigger>
<DialogContent className="h-[420px] lg:max-w-[600px] ">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Export</span>
<IconComponent
name="Download"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{EXPORT_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<BaseModal size="smaller" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
<span className="pr-2">Export</span>
<IconComponent
name="Download"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
name={name}
description={description}
@ -64,42 +38,42 @@ export default function ExportModal(): JSX.Element {
setDescription={setDescription}
updateFlow={updateFlow}
/>
<div className="flex items-center space-x-2">
<div className="mt-3 flex items-center space-x-2">
<Checkbox
id="terms"
onCheckedChange={(event: boolean): void => {
setChecked(event);
}}
/>
<label htmlFor="terms" className="export-modal-save-api text-sm">
<label htmlFor="terms" className="export-modal-save-api text-sm ">
Save with my API keys
</label>
</div>
</BaseModal.Content>
<DialogFooter>
<Button
onClick={() => {
if (checked)
downloadFlow(
flows.find((f) => f.id === tabId),
name,
description
);
else
downloadFlow(
removeApiKeys(flows.find((f) => f.id === tabId)),
name,
description
);
closePopUp();
}}
type="submit"
>
Download Flow
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<BaseModal.Footer>
<Button
onClick={() => {
if (checked)
downloadFlow(
flows.find((f) => f.id === tabId),
name,
description
);
else
downloadFlow(
removeApiKeys(flows.find((f) => f.id === tabId)),
name,
description
);
setOpen(false);
}}
type="submit"
>
Download Flow
</Button>
</BaseModal.Footer>
</BaseModal>
);
}
});
export default ExportModal;

View file

@ -2,23 +2,16 @@ import { useContext, useRef, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import BaseModal from "../baseModal";
import { FlowSettingsPropsType } from "../../types/components";
export default function FlowSettingsModal(): JSX.Element {
const [open, setOpen] = useState(true);
const { closePopUp } = useContext(PopUpContext);
export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const { setErrorData, setSuccessData } = useContext(alertContext);
const ref = useRef();
const { flows, tabId, updateFlow, setTabsState, saveFlow } =
@ -28,34 +21,21 @@ export default function FlowSettingsModal(): JSX.Element {
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
function setModalOpen(x: boolean): void {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
function handleClick(): void {
let savedFlow = flows.find((f) => f.id === tabId);
savedFlow.name = name;
savedFlow.description = description;
saveFlow(savedFlow);
setSuccessData({ title: "Changes saved successfully" });
closePopUp();
setOpen(false);
}
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger asChild></DialogTrigger>
<DialogContent className="h-[390px] lg:max-w-[600px]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Settings </span>
<IconComponent name="Settings2" className="mr-2 h-4 w-4 " />
</DialogTitle>
<DialogDescription>{SETTINGS_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<BaseModal open={open} setOpen={setOpen} size="smaller">
<BaseModal.Header description={SETTINGS_DIALOG_SUBTITLE}>
<span className="pr-2">Settings</span>
<IconComponent name="Settings2" className="mr-2 h-4 w-4 " />
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
name={name}
description={description}
@ -65,13 +45,13 @@ export default function FlowSettingsModal(): JSX.Element {
setDescription={setDescription}
updateFlow={updateFlow}
/>
</BaseModal.Content>
<DialogFooter>
<Button onClick={handleClick} type="submit">
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<BaseModal.Footer>
<Button onClick={handleClick} type="submit">
Save
</Button>
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -1,6 +1,7 @@
import { useEffect } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { chatInputType } from "../../../types/components";
import { Textarea } from "../../../components/ui/textarea";
import { classNames } from "../../../utils/utils";
export default function ChatInput({
@ -26,8 +27,8 @@ export default function ChatInput({
return (
<div className="relative">
<textarea
onKeyDown={(event): void => {
<Textarea
onKeyDown={(event) => {
if (event.key === "Enter" && !lockChat && !event.shiftKey) {
sendMessage();
}
@ -69,10 +70,10 @@ export default function ChatInput({
className={classNames(
"form-modal-send-button",
noInput
? "bg-indigo-600 text-background"
? "bg-high-indigo text-background"
: chatValue === ""
? "text-primary"
: "bg-emerald-600 text-background"
: "bg-chat-send text-background"
)}
disabled={lockChat}
onClick={(): void => sendMessage()}

View file

@ -7,12 +7,13 @@ import remarkMath from "remark-math";
import MaleTechnology from "../../../assets/male-technologist.png";
import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils/utils";
import FileCard from "../fileComponent";
import { CodeBlock } from "./codeBlock";
import { chatMessagePropsType } from "../../../types/components";
export default function ChatMessage({
chat,
lockChat,
@ -75,57 +76,75 @@ export default function ChatMessage({
<div className="w-full dark:text-white">
<div className="w-full">
{useMemo(
() => (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary
dark:prose-invert sm:w-[30vw] sm:max-w-[30vw] lg:w-[40vw] lg:max-w-[40vw]"
components={{
code: ({
node,
inline,
className,
children,
...props
}) => {
if (children.length) {
if (children[0] === "▍") {
return (
<span className="form-modal-markdown-span">
</span>
() =>
chat.message.toString() === "" && lockChat ? (
<IconComponent
name="MoreHorizontal"
className="h-8 w-8 animate-pulse"
/>
) : (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary dark:prose-invert
sm:w-[30vw] sm:max-w-[30vw] lg:w-[40vw] lg:max-w-[40vw]"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
},
code: ({
node,
inline,
className,
children,
...props
}) => {
if (children.length) {
if (children[0] === "▍") {
return (
<span className="form-modal-markdown-span">
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
const match = /language-(\w+)/.exec(
className || ""
);
}
const match = /language-(\w+)/.exec(
className || ""
);
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
),
return !inline ? (
<CodeTabsComponent
isMessage
tabs={[
{
name: (match && match[1]) || "",
mode: (match && match[1]) || "",
image:
"https://curl.se/logo/curl-symbol-transparent.png",
language: (match && match[1]) || "",
code: String(children).replace(/\n$/, ""),
},
]}
activeTab={"0"}
setActiveTab={() => {}}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
),
[chat.message, chat.message.toString()]
)}
</div>

View file

@ -32,7 +32,7 @@ export default function FormModal({
setOpen,
}: {
open: boolean;
setOpen: Function;
setOpen: (open: boolean) => void;
flow: FlowType;
}): JSX.Element {
const { tabsState, setTabsState } = useContext(TabsContext);
@ -389,8 +389,8 @@ export default function FormModal({
}
return (
<Dialog open={open} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>
{tabsState[flow.id].formKeysData && (
<DialogContent className="min-w-[80vw]">
<DialogHeader>
@ -398,7 +398,7 @@ export default function FormModal({
<span className="pr-2">Chat</span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</DialogTitle>

View file

@ -1,10 +1,9 @@
import { useContext, useEffect, useRef, useState } from "react";
import { ReactNode, useContext, useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import { DialogTitle } from "../../components/ui/dialog";
import { Textarea } from "../../components/ui/textarea";
import {
INVALID_CHARACTERS,
@ -15,8 +14,6 @@ import {
} from "../../constants/constants";
import { TypeModal } from "../../constants/enums";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { postValidatePrompt } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import {
@ -36,6 +33,7 @@ export default function GenericModal({
type,
nodeClass,
setNodeClass,
children,
}: genericModalPropsType): JSX.Element {
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
@ -43,17 +41,9 @@ export default function GenericModal({
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlight, setWordsHighlight] = useState([]);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData, setNoticeData } =
useContext(alertContext);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean): void {
if (x === false) {
setCloseEdit("generic");
closePopUp();
}
}
const divRef = useRef(null);
const divRefPrompt = useRef(null);
@ -97,6 +87,10 @@ export default function GenericModal({
}
}, [inputValue, type]);
useEffect(() => {
setInputValue(value);
}, [value]);
const coloredContent = (inputValue || "")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
@ -130,7 +124,6 @@ export default function GenericModal({
postValidatePrompt(field_name, inputValue, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data?.frontend_node);
let inputVariables = apiReturn.data.input_variables ?? [];
if (inputVariables && inputVariables.length === 0) {
setIsEdit(true);
@ -142,6 +135,7 @@ export default function GenericModal({
setSuccessData({
title: "Prompt is ready",
});
setNodeClass(apiReturn.data?.frontend_node);
setModalOpen(closeModal);
setValue(inputValue);
}
@ -162,8 +156,11 @@ export default function GenericModal({
});
}
const [modalOpen, setModalOpen] = useState(false);
return (
<BaseModal open={true} setOpen={setModalOpen}>
<BaseModal open={modalOpen} setOpen={setModalOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header
description={(() => {
switch (myModalTitle) {
@ -178,14 +175,12 @@ export default function GenericModal({
}
})()}
>
<DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
<IconComponent
name="FileText"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
<span className="pr-2">{myModalTitle}</span>
<IconComponent
name="FileText"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full flex-col">
@ -269,7 +264,7 @@ export default function GenericModal({
))}
</div>
</div>
<span className="mt-1 text-xs text-muted-foreground">
<span className="mt-2 text-xs text-muted-foreground">
Prompt variables can be created with any chosen name inside
curly brackets, e.g. {"{variable_name}"}
</span>
@ -279,11 +274,11 @@ export default function GenericModal({
<Button
onClick={() => {
switch (myModalType) {
case 1:
case TypeModal.TEXT:
setValue(inputValue);
setModalOpen(false);
break;
case 2:
case TypeModal.PROMPT:
!inputValue || inputValue === ""
? setModalOpen(false)
: validatePrompt(false);

View file

@ -17,7 +17,6 @@ import {
} from "../../components/ui/dialog";
import { IMPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
@ -27,20 +26,11 @@ import ButtonBox from "./buttonBox";
export default function ImportModal(): JSX.Element {
const [open, setOpen] = useState(true);
const { setErrorData } = useContext(alertContext);
const { closePopUp } = useContext(PopUpContext);
const ref = useRef();
const [showExamples, setShowExamples] = useState(false);
const [loadingExamples, setLoadingExamples] = useState(false);
const [examples, setExamples] = useState<FlowType[]>([]);
const { uploadFlow, addFlow } = useContext(TabsContext);
function setModalOpen(x: boolean): void {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
function handleExamples(): void {
setLoadingExamples(true);
@ -57,8 +47,10 @@ export default function ImportModal(): JSX.Element {
);
}
const [modalOpen, setModalOpen] = useState(false);
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent
className={classNames(

View file

@ -27,6 +27,7 @@ import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType } from "../../../../types/flow";
import { isValidConnection } from "../../../../utils/reactflowUtils";
import { isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import ExtraSidebar from "../extraSidebarComponent";
@ -38,7 +39,6 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
let {
updateFlow,
uploadFlow,
disableCopyPaste,
addFlow,
getNodeId,
paste,
@ -63,34 +63,34 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
// this effect is used to attach the global event handlers
const onKeyDown = (event: KeyboardEvent) => {
if (
(event.ctrlKey || event.metaKey) &&
event.key === "c" &&
lastSelection &&
!disableCopyPaste
) {
event.preventDefault();
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection &&
!disableCopyPaste
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
) {
event.preventDefault();
if (!isWrappedWithClass(event, "nocopy")) {
if (
(event.ctrlKey || event.metaKey) &&
event.key === "c" &&
lastSelection
) {
event.preventDefault();
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
) {
event.preventDefault();
}
}
};
const handleMouseMove = (event: MouseEvent) => {
@ -124,7 +124,7 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
updateFlow(flow);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]);
}, [edges]);
//update flow when tabs change
useEffect(() => {
setNodes(flow?.data?.nodes ?? []);
@ -355,8 +355,6 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
setLastSelection(flow);
}, []);
const { setDisableCopyPaste } = useContext(TabsContext);
return (
<div className="flex h-full overflow-hidden">
<ExtraSidebar />
@ -378,15 +376,6 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
});
}}
edges={edges}
onPaneClick={() => {
setDisableCopyPaste(false);
}}
onPaneMouseLeave={() => {
setDisableCopyPaste(true);
}}
onPaneMouseEnter={() => {
setDisableCopyPaste(false);
}}
onNodesChange={onNodesChangeMod}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
@ -407,9 +396,6 @@ export default function Page({ flow }: { flow: FlowType }): JSX.Element {
onDrop={onDrop}
onNodesDelete={onDelete}
onSelectionChange={onSelectionChange}
nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}

View file

@ -1,10 +1,9 @@
import { Search } from "lucide-react";
import { useContext, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
@ -20,7 +19,6 @@ import DisclosureComponent from "../DisclosureComponent";
export default function ExtraSidebar(): JSX.Element {
const { data } = useContext(typesContext);
const { openPopUp } = useContext(PopUpContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow } =
useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
@ -58,6 +56,7 @@ export default function ExtraSidebar(): JSX.Element {
return ret;
});
}
const flow = flows.find((f) => f.id === tabId);
return (
<div className="side-bar-arrangement">
@ -74,31 +73,27 @@ export default function ExtraSidebar(): JSX.Element {
</ShadTooltip>
<ShadTooltip content="Export" side="top">
<button
className={classNames("extra-side-bar-buttons")}
onClick={(event): void => {
openPopUp(<ExportModal />);
}}
>
<IconComponent name="FileDown" className="side-bar-button-size" />
</button>
<ExportModal>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent name="FileDown" className="side-bar-button-size" />
</div>
</ExportModal>
</ShadTooltip>
<ShadTooltip content="Code" side="top">
<button
className={classNames("extra-side-bar-buttons")}
onClick={(event): void => {
openPopUp(<ApiModal flow={flows.find((f) => f.id === tabId)} />);
}}
>
<IconComponent name="Code2" className="side-bar-button-size" />
</button>
{flow && flow.data && (
<ApiModal flow={flow}>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent name="Code2" className="side-bar-button-size" />
</div>
</ApiModal>
)}
</ShadTooltip>
<ShadTooltip content="Save" side="top">
<button
className="extra-side-bar-buttons"
onClick={(event): void => {
saveFlow(flows.find((f) => f.id === tabId));
onClick={(event) => {
saveFlow(flow);
setSuccessData({ title: "Changes saved successfully" });
}}
disabled={!isPending}
@ -115,12 +110,12 @@ export default function ExtraSidebar(): JSX.Element {
</div>
<Separator />
<div className="side-bar-search-div-placement">
<input
<Input
type="text"
name="search"
id="search"
placeholder="Search"
className="input-search"
className="nopan nodrag noundo nocopy input-search"
onChange={(e) => {
handleSearchInput(e.target.value);
// Set search input state
@ -128,7 +123,11 @@ export default function ExtraSidebar(): JSX.Element {
}}
/>
<div className="search-icon">
<Search size={20} strokeWidth={1.5} className="text-primary" />
<IconComponent
name="Search"
className={"h-5 w-5 stroke-[1.5] text-primary"}
aria-hidden="true"
/>
</div>
</div>

View file

@ -7,24 +7,24 @@ import EditNodeModal from "../../../../modals/EditNodeModal";
import { nodeToolbarPropsType } from "../../../../types/components";
import { classNames } from "../../../../utils/utils";
const NodeToolbarComponent = (props: nodeToolbarPropsType): JSX.Element => {
export default function NodeToolbarComponent({ data, setData, deleteNode }: nodeToolbarPropsType): JSX.Element {
const [nodeLength, setNodeLength] = useState(
Object.keys(props.data.node.template).filter(
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
props.data.node.template[t].show &&
(props.data.node.template[t].type === "str" ||
props.data.node.template[t].type === "bool" ||
props.data.node.template[t].type === "float" ||
props.data.node.template[t].type === "code" ||
props.data.node.template[t].type === "prompt" ||
props.data.node.template[t].type === "file" ||
props.data.node.template[t].type === "Any" ||
props.data.node.template[t].type === "int")
data.node.template[t].show &&
(data.node.template[t].type === "str" ||
data.node.template[t].type === "bool" ||
data.node.template[t].type === "float" ||
data.node.template[t].type === "code" ||
data.node.template[t].type === "prompt" ||
data.node.template[t].type === "file" ||
data.node.template[t].type === "Any" ||
data.node.template[t].type === "int")
).length
);
const { setLastCopiedSelection, paste } = useContext(TabsContext);
const { paste } = useContext(TabsContext);
const reactFlowInstance = useReactFlow();
return (
<>
@ -34,7 +34,7 @@ const NodeToolbarComponent = (props: nodeToolbarPropsType): JSX.Element => {
<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"
onClick={() => {
props.deleteNode(props.data.id);
deleteNode(data.id);
}}
>
<IconComponent name="Trash2" className="h-4 w-4" />
@ -50,14 +50,14 @@ const NodeToolbarComponent = (props: nodeToolbarPropsType): JSX.Element => {
event.preventDefault();
paste(
{
nodes: [reactFlowInstance.getNode(props.data.id)],
nodes: [reactFlowInstance.getNode(data.id)],
edges: [],
},
{
x: 50,
y: 10,
paneX: reactFlowInstance.getNode(props.data.id).position.x,
paneY: reactFlowInstance.getNode(props.data.id).position.y,
paneX: reactFlowInstance.getNode(data.id).position.x,
paneY: reactFlowInstance.getNode(data.id).position.y,
}
);
}}
@ -68,25 +68,23 @@ const NodeToolbarComponent = (props: nodeToolbarPropsType): JSX.Element => {
<ShadTooltip
content={
props.data.node.documentation === ""
? "Coming Soon"
: "Documentation"
data.node.documentation === "" ? "Coming Soon" : "Documentation"
}
side="top"
>
<a
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" +
(props.data.node.documentation === ""
(data.node.documentation === ""
? " text-muted-foreground"
: " text-foreground")
)}
target="_blank"
rel="noopener noreferrer"
href={props.data.node.documentation}
href={data.node.documentation}
// deactivate link if no documentation is provided
onClick={(event) => {
if (props.data.node.documentation === "") {
if (data.node.documentation === "") {
event.preventDefault();
}
}}
@ -96,28 +94,27 @@ const NodeToolbarComponent = (props: nodeToolbarPropsType): JSX.Element => {
</ShadTooltip>
<ShadTooltip content="Edit" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center rounded-r-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" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
onClick={(event) => {
if (nodeLength == 0) {
event.preventDefault();
}
event.preventDefault();
props.openPopUp(<EditNodeModal data={props.data} />);
}}
>
<IconComponent name="Settings2" className="h-4 w-4 " />
</button>
<div>
<EditNodeModal
data={data}
setData={setData}
nodeLength={nodeLength}
>
<div
className={classNames(
"relative -ml-px inline-flex items-center rounded-r-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" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
>
<IconComponent name="Settings2" className="h-4 w-4 " />
</div>
</EditNodeModal>
</div>
</ShadTooltip>
</span>
</div>
</>
);
};
export default NodeToolbarComponent;
}

View file

@ -1,5 +1,5 @@
import { ForwardRefExoticComponent, ReactElement, ReactNode, RefAttributes, SVGProps } from "react";
import { APIClassType } from "../api";
import { APIClassType, APITemplateType } from "../api";
import { FlowStyleType, FlowType, NodeDataType } from "../flow/index";
import { typesContextType } from "../typesContext";
import { ChatMessageType } from "../chat";
@ -9,7 +9,6 @@ export type InputComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
password: boolean;
disableCopyPaste?: boolean;
editNode?: boolean;
onChangePass?: (value: boolean | boolean) => void;
showPass?: boolean;
@ -30,6 +29,7 @@ export type DropDownComponentType = {
};
export type ParameterComponentType = {
data: NodeDataType;
setData: (value: NodeDataType) => void;
title: string;
id: string;
color: string;
@ -47,7 +47,6 @@ export type InputListComponentType = {
onChange: (value: string[]) => void;
disabled: boolean;
editNode?: boolean;
onAddInput?: (value?: string[]) => void;
};
export type TextAreaComponentType = {
@ -95,7 +94,6 @@ export type DisclosureComponentType = {
export type FloatComponentType = {
value: string;
disabled?: boolean;
disableCopyPaste?: boolean;
onChange: (value: string) => void;
editNode?: boolean;
};
@ -175,7 +173,7 @@ export type InputProps = {
name: string | null;
description: string | null;
maxLength?: number;
flows: Array<{ id: string; name: string }>;
flows: Array<{ id: string; name: string; description: string }>;
tabId: string;
setName: (name: string) => void;
setDescription: (description: string) => void;
@ -224,7 +222,7 @@ export type nodeToolbarType = {
description: string;
display_name: string;
documentation: string;
template: object;
template: APITemplateType;
};
value: void;
};
@ -290,7 +288,7 @@ export type fileCardPropsType = {
export type nodeToolbarPropsType = {
data: NodeDataType;
deleteNode: (idx: string) => void;
openPopUp: (element: JSX.Element) => void;
setData: (newState: {}) => void;
};
export type parsedDataType = {
@ -321,6 +319,7 @@ export type codeAreaModalPropsType = {
value: string;
nodeClass: APIClassType;
setNodeClass: (Class: APIClassType) => void;
children: ReactNode;
};
export type chatMessagePropsType = {
@ -344,6 +343,7 @@ export type genericModalPropsType = {
type: number;
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType) => void;
children: ReactNode;
};
export type buttonBoxPropsType = {
@ -356,3 +356,8 @@ export type buttonBoxPropsType = {
deactivate?: boolean;
size: "small" | "medium" | "big";
};
export type FlowSettingsPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
};

View file

@ -20,9 +20,6 @@ export type TabsContextType = {
uploadFlows: () => void;
uploadFlow: (newFlow?: boolean, file?: File) => void;
hardReset: () => void;
//disable CopyPaste
disableCopyPaste: boolean;
setDisableCopyPaste: (value: boolean) => void;
getNodeId: (nodeType: string) => string;
tabsState: TabsState;
setTabsState: Dispatch<SetStateAction<TabsState>>;

View file

@ -41,6 +41,7 @@ import {
MessageSquare,
MessagesSquare,
MoonIcon,
MoreHorizontal,
Paperclip,
Plus,
Redo,
@ -269,6 +270,7 @@ export const nodeIconsLucide: iconsType = {
Copy,
Upload,
MessageSquare,
MoreHorizontal,
};
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
const sourceId = edge.source;

View file

@ -90,12 +90,10 @@ export function checkUpperWords(str: string): string {
return words.join(" ");
}
export function groupByFamily(
data: APIClassType,
baseClasses: string,
left: boolean,
type: string
): groupedObjType[] {
export const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`);
export function groupByFamily(data: APIClassType, baseClasses: string, left: boolean, type: string): groupedObjType[] {
let parentOutput: string;
let arrOfParent: string[] = [];
let arrOfType: { family: string; type: string; component: string }[] = [];
@ -283,7 +281,7 @@ export function buildTweakObject(tweak: tweakType[]): string {
});
});
const tweakString = JSON.stringify(tweak, null, 2);
const tweakString = JSON.stringify(tweak.at(-1), null, 2);
return tweakString;
}

View file

@ -29,6 +29,8 @@ module.exports = {
},
extend: {
colors: {
"low-indigo": "var(--low-indigo)",
"chat-send": "var(--chat-send)",
connection: "var(--connection)",
"almost-dark-gray": "var(--almost-dark-gray)",
"almost-light-blue": "var(--almost-light-blue)",