Merge branch 'feature/store' into bug/undo-copy
This commit is contained in:
commit
082ea5282f
37 changed files with 421 additions and 326 deletions
|
|
@ -47,12 +47,3 @@ def build_input_keys_response(langchain_object, artifacts):
|
|||
input_keys_response["template"] = langchain_object.prompt.template
|
||||
|
||||
return input_keys_response
|
||||
|
||||
|
||||
def get_new_key(dictionary, original_key):
|
||||
counter = 1
|
||||
new_key = original_key + " (" + str(counter) + ")"
|
||||
while new_key in dictionary:
|
||||
counter += 1
|
||||
new_key = original_key + " (" + str(counter) + ")"
|
||||
return new_key
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ async def stream_build(
|
|||
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
||||
|
||||
vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
||||
if vertex_id in graph.top_level_nodes:
|
||||
if vertex_id in graph.top_level_vertices:
|
||||
response = {
|
||||
"valid": valid,
|
||||
"params": params,
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ async def process(
|
|||
if flow.data is None:
|
||||
raise ValueError(f"Flow {flow_id} has no data")
|
||||
graph_data = flow.data
|
||||
task_result = None
|
||||
if tweaks:
|
||||
try:
|
||||
graph_data = process_tweaks(graph_data, tweaks)
|
||||
|
|
@ -160,6 +161,10 @@ async def get_task_status(task_id: str):
|
|||
result = None
|
||||
if task.ready():
|
||||
result = task.result
|
||||
# If result isinstance of Exception, can we get the traceback?
|
||||
if isinstance(result, Exception):
|
||||
logger.exception(task.traceback)
|
||||
|
||||
if isinstance(result, dict) and "result" in result:
|
||||
result = result["result"]
|
||||
elif hasattr(result, "result"):
|
||||
|
|
@ -167,6 +172,10 @@ async def get_task_status(task_id: str):
|
|||
|
||||
if task is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
if task.status == "FAILURE":
|
||||
result = str(task.result)
|
||||
logger.error(f"Task {task_id} failed: {task.traceback}")
|
||||
|
||||
return TaskStatusResponse(status=task.status, result=result)
|
||||
|
||||
|
||||
|
|
@ -237,3 +246,9 @@ async def custom_component_update(
|
|||
component_node = build_langchain_template_custom_component(component, user_id=user.id, update_field=raw_code.field)
|
||||
# Update the field
|
||||
return component_node
|
||||
# Update the field
|
||||
return component_node
|
||||
# Update the field
|
||||
return component_node
|
||||
# Update the field
|
||||
return component_node
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@ import os
|
|||
|
||||
langflow_redis_host = os.environ.get("LANGFLOW_REDIS_HOST")
|
||||
langflow_redis_port = os.environ.get("LANGFLOW_REDIS_PORT")
|
||||
if "BROKER_URL" in os.environ and "RESULT_BACKEND" in os.environ:
|
||||
# RabbitMQ
|
||||
broker_url = os.environ.get("BROKER_URL", "amqp://localhost")
|
||||
result_backend = os.environ.get("RESULT_BACKEND", "redis://localhost:6379/0")
|
||||
elif langflow_redis_host and langflow_redis_port:
|
||||
# broker default user
|
||||
|
||||
if langflow_redis_host and langflow_redis_port:
|
||||
broker_url = f"redis://{langflow_redis_host}:{langflow_redis_port}/0"
|
||||
result_backend = f"redis://{langflow_redis_host}:{langflow_redis_port}/0"
|
||||
else:
|
||||
# RabbitMQ
|
||||
mq_user = os.environ.get("RABBITMQ_DEFAULT_USER", "langflow")
|
||||
mq_password = os.environ.get("RABBITMQ_DEFAULT_PASS", "langflow")
|
||||
broker_url = os.environ.get("BROKER_URL", f"amqp://{mq_user}:{mq_password}@localhost:5672//")
|
||||
result_backend = os.environ.get("RESULT_BACKEND", "redis://localhost:6379/0")
|
||||
# tasks should be json or pickle
|
||||
accept_content = ["json", "pickle"]
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ class TargetHandle(BaseModel):
|
|||
|
||||
class Edge:
|
||||
def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
|
||||
self.source_id: str = source.id
|
||||
self.target_id: str = target.id
|
||||
self.source_id: str = source.id if source else ""
|
||||
self.target_id: str = target.id if target else ""
|
||||
if data := edge.get("data", {}):
|
||||
self._source_handle = data.get("sourceHandle", {})
|
||||
self._target_handle = data.get("targetHandle", {})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
from typing import Dict, Generator, List, Type, Union
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from loguru import logger
|
||||
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||
from langflow.graph.graph.utils import process_flow
|
||||
|
|
@ -10,6 +8,7 @@ from langflow.graph.vertex.base import Vertex
|
|||
from langflow.graph.vertex.types import FileToolVertex, LLMVertex, ToolkitVertex
|
||||
from langflow.interface.tools.constants import FILE_TOOLS
|
||||
from langflow.utils import payload
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class Graph:
|
||||
|
|
@ -71,7 +70,7 @@ class Graph:
|
|||
def _build_graph(self) -> None:
|
||||
"""Builds the graph from the vertices and edges."""
|
||||
self.vertices = self._build_vertices()
|
||||
self.vertex_ids = [vertex.id for vertex in self.vertices]
|
||||
self.vertex_map = {vertex.id: vertex for vertex in self.vertices}
|
||||
self.edges = self._build_edges()
|
||||
|
||||
# This is a hack to make sure that the LLM vertex is sent to
|
||||
|
|
@ -108,7 +107,7 @@ class Graph:
|
|||
|
||||
def get_vertex(self, vertex_id: str) -> Union[None, Vertex]:
|
||||
"""Returns a vertex by id."""
|
||||
return next((vertex for vertex in self.vertices if vertex.id == vertex_id), None)
|
||||
return self.vertex_map.get(vertex_id)
|
||||
|
||||
def get_vertex_edges(self, vertex_id: str) -> List[Edge]:
|
||||
"""Returns a list of edges for a given vertex."""
|
||||
|
|
@ -154,8 +153,8 @@ class Graph:
|
|||
if state[vertex] == 0:
|
||||
state[vertex] = 1
|
||||
for edge in vertex.edges:
|
||||
if edge.source == vertex:
|
||||
dfs(edge.target)
|
||||
if edge.source_id == vertex.id:
|
||||
dfs(self.get_vertex(edge.target_id))
|
||||
state[vertex] = 2
|
||||
sorted_vertices.append(vertex)
|
||||
|
||||
|
|
@ -250,3 +249,4 @@ class Graph:
|
|||
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import ast
|
||||
import inspect
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING, Any, Coroutine, Dict, List, Optional
|
||||
|
||||
from langflow.graph.utils import UnbuiltObject
|
||||
from langflow.interface.initialize import loading
|
||||
|
|
@ -73,8 +73,8 @@ class Vertex:
|
|||
self.parent_node_id = state["parent_node_id"]
|
||||
self.parent_is_top_level = state["parent_is_top_level"]
|
||||
|
||||
def set_top_level(self, top_level_nodes: List[str]) -> None:
|
||||
self.parent_is_top_level = self.parent_node_id in top_level_nodes
|
||||
def set_top_level(self, top_level_vertices: List[str]) -> None:
|
||||
self.parent_is_top_level = self.parent_node_id in top_level_vertices
|
||||
|
||||
def _parse_data(self) -> None:
|
||||
self.data = self._data["data"]
|
||||
|
|
@ -245,7 +245,10 @@ class Vertex:
|
|||
|
||||
if self.is_task and self.task_id is not None:
|
||||
task = self.get_task()
|
||||
|
||||
result = task.get(timeout=timeout)
|
||||
if isinstance(result, Coroutine):
|
||||
result = await result
|
||||
if result is not None: # If result is ready
|
||||
self._update_built_object_and_artifacts(result)
|
||||
return self._built_object
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class AgentVertex(Vertex):
|
|||
|
||||
def _set_tools_and_chains(self) -> None:
|
||||
for edge in self.edges:
|
||||
if not hasattr(edge, "source"):
|
||||
if not hasattr(edge, "source_id"):
|
||||
continue
|
||||
source_node = self.graph.get_vertex(edge.source_id)
|
||||
if isinstance(source_node, (ToolVertex, ToolkitVertex)):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from uuid import UUID
|
|||
|
||||
from cachetools import LRUCache, cached
|
||||
from fastapi import HTTPException
|
||||
from langflow.api.utils import get_new_key
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
from langflow.interface.custom.base import custom_component_creator
|
||||
|
|
@ -416,8 +415,8 @@ def build_valid_menu(valid_components):
|
|||
|
||||
component_template = build_langchain_template_custom_component(component_extractor)
|
||||
component_template["output_types"] = component_output_types
|
||||
full_path = f"{menu_path}/{component.get('file')}"
|
||||
component_template["full_path"] = full_path
|
||||
# full_path = f"{menu_path}/{component.get('file')}"
|
||||
# component_template["full_path"] = full_path
|
||||
if len(component_output_types) == 1:
|
||||
component_name = component_output_types[0]
|
||||
else:
|
||||
|
|
@ -476,15 +475,24 @@ def build_invalid_menu(invalid_components):
|
|||
return invalid_menu
|
||||
|
||||
|
||||
def get_new_key(dictionary, original_key):
|
||||
counter = 1
|
||||
new_key = original_key + " (" + str(counter) + ")"
|
||||
while new_key in dictionary:
|
||||
counter += 1
|
||||
new_key = original_key + " (" + str(counter) + ")"
|
||||
return new_key
|
||||
|
||||
|
||||
def merge_nested_dicts_with_renaming(dict1, dict2):
|
||||
for key, value in dict2.items():
|
||||
if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict):
|
||||
for sub_key, sub_value in value.items():
|
||||
if sub_key in dict1[key]:
|
||||
new_key = get_new_key(dict1[key], sub_key)
|
||||
dict1[key][new_key] = sub_value
|
||||
else:
|
||||
dict1[key][sub_key] = sub_value
|
||||
# if sub_key in dict1[key]:
|
||||
# new_key = get_new_key(dict1[key], sub_key)
|
||||
# dict1[key][new_key] = sub_value
|
||||
# else:
|
||||
dict1[key][sub_key] = sub_value
|
||||
else:
|
||||
dict1[key] = value
|
||||
return dict1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Coroutine, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.schema import AgentAction, Document
|
||||
|
|
@ -138,6 +138,8 @@ def generate_result(langchain_object: Union[Chain, VectorStore], inputs: dict):
|
|||
result = langchain_object.dict()
|
||||
else:
|
||||
logger.warning(f"Unknown langchain_object type: {type(langchain_object)}")
|
||||
if isinstance(langchain_object, Coroutine):
|
||||
result = asyncio.run(langchain_object)
|
||||
result = langchain_object
|
||||
|
||||
return result
|
||||
|
|
|
|||
67
src/backend/langflow/services/cache/utils.py
vendored
67
src/backend/langflow/services/cache/utils.py
vendored
|
|
@ -1,24 +1,23 @@
|
|||
import base64
|
||||
import contextlib
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict
|
||||
from platformdirs import user_cache_dir
|
||||
|
||||
from fastapi import UploadFile
|
||||
from langflow.api.v1.schemas import BuildStatus
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
from platformdirs import user_cache_dir
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
from langflow.api.v1.schemas import BuildStatus
|
||||
|
||||
CACHE: Dict[str, Any] = {}
|
||||
|
||||
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||
|
||||
PREFIX = "langflow_cache"
|
||||
|
||||
|
||||
def create_cache_folder(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
|
@ -33,50 +32,6 @@ def create_cache_folder(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
def memoize_dict(maxsize=128):
|
||||
cache = OrderedDict()
|
||||
hash_to_key = {} # Mapping from hash to cache key
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
hashed = compute_dict_hash(args[0])
|
||||
key = (func.__name__, hashed, frozenset(kwargs.items()))
|
||||
if key not in cache:
|
||||
result = func(*args, **kwargs)
|
||||
cache[key] = result
|
||||
hash_to_key[hashed] = key # Store the mapping
|
||||
if len(cache) > maxsize:
|
||||
oldest_key = next(iter(cache))
|
||||
oldest_hash = oldest_key[1]
|
||||
del cache[oldest_key]
|
||||
del hash_to_key[oldest_hash]
|
||||
else:
|
||||
result = cache[key]
|
||||
|
||||
wrapper.session_id = hashed # Store hash in the wrapper
|
||||
return result
|
||||
|
||||
def clear_cache():
|
||||
cache.clear()
|
||||
hash_to_key.clear()
|
||||
|
||||
def get_result_by_session_id(session_id):
|
||||
key = hash_to_key.get(session_id)
|
||||
return cache.get(key) if key is not None else None
|
||||
|
||||
wrapper.clear_cache = clear_cache # type: ignore
|
||||
wrapper.get_result_by_session_id = get_result_by_session_id # type: ignore
|
||||
wrapper.hash = None
|
||||
wrapper.cache = cache # type: ignore
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
PREFIX = "langflow_cache"
|
||||
|
||||
|
||||
@create_cache_folder
|
||||
def clear_old_cache_files(max_cache_size: int = 3):
|
||||
cache_dir = Path(tempfile.gettempdir()) / PREFIX
|
||||
|
|
@ -90,14 +45,6 @@ def clear_old_cache_files(max_cache_size: int = 3):
|
|||
os.remove(cache_file)
|
||||
|
||||
|
||||
def compute_dict_hash(graph_data):
|
||||
graph_data = filter_json(graph_data)
|
||||
|
||||
cleaned_graph_json = orjson_dumps(graph_data, sort_keys=True)
|
||||
|
||||
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def filter_json(json_data):
|
||||
filtered_data = json_data.copy()
|
||||
|
||||
|
|
@ -203,9 +150,11 @@ def save_uploaded_file(file: UploadFile, folder_name):
|
|||
return file_path
|
||||
|
||||
|
||||
def update_build_status(cache_service, flow_id: str, status: BuildStatus):
|
||||
def update_build_status(cache_service, flow_id: str, status: "BuildStatus"):
|
||||
cached_flow = cache_service[flow_id]
|
||||
if cached_flow is None:
|
||||
raise ValueError(f"Flow {flow_id} not found in cache")
|
||||
cached_flow["status"] = status
|
||||
cache_service[flow_id] = cached_flow
|
||||
cached_flow["status"] = status
|
||||
cache_service[flow_id] = cached_flow
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from langflow.interface.run import build_sorted_vertices
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.cache.utils import compute_dict_hash
|
||||
from langflow.services.session.utils import session_id_generator
|
||||
from langflow.services.session.utils import compute_dict_hash, session_id_generator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.cache.base import BaseCacheService
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
import hashlib
|
||||
import random
|
||||
import string
|
||||
|
||||
from langflow.services.cache.utils import filter_json
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
|
||||
def session_id_generator(size=6):
|
||||
return "".join(random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=size))
|
||||
|
||||
|
||||
def compute_dict_hash(graph_data):
|
||||
graph_data = filter_json(graph_data)
|
||||
|
||||
cleaned_graph_json = orjson_dumps(graph_data, sort_keys=True)
|
||||
|
||||
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Any, Callable, Coroutine, Union
|
||||
from langflow.utils.logger import configure
|
||||
from loguru import logger
|
||||
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.task.backends.anyio import AnyIOBackend
|
||||
from langflow.services.task.backends.base import TaskBackend
|
||||
from langflow.services.task.utils import get_celery_worker_status
|
||||
from langflow.utils.logger import configure
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def check_celery_availability():
|
||||
|
|
@ -60,7 +61,11 @@ class TaskService(Service):
|
|||
if not hasattr(task_func, "apply"):
|
||||
raise ValueError(f"Task function {task_func} does not have an apply method")
|
||||
task = task_func.apply(args=args, kwargs=kwargs)
|
||||
|
||||
result = task.get()
|
||||
# if result is coroutine
|
||||
if isinstance(result, Coroutine):
|
||||
result = await result
|
||||
return task.id, result
|
||||
|
||||
async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||
|
|
@ -71,3 +76,6 @@ class TaskService(Service):
|
|||
|
||||
def get_task(self, task_id: Union[int, str]) -> Any:
|
||||
return self.backend.get_task(task_id)
|
||||
|
||||
def get_task(self, task_id: Union[int, str]) -> Any:
|
||||
return self.backend.get_task(task_id)
|
||||
|
|
|
|||
|
|
@ -235,6 +235,12 @@ def build_class_constructor(compiled_class, exec_globals, class_name):
|
|||
:param class_name: Name of the class
|
||||
:return: Constructor function for the class
|
||||
"""
|
||||
# Add basic imports from typing module
|
||||
# List, Dict, Tuple, Union, Optional
|
||||
# to the global scope
|
||||
for name in ["List", "Dict", "Tuple", "Union", "Optional"]:
|
||||
exec_globals[name] = getattr(importlib.import_module("typing"), name)
|
||||
|
||||
exec(compiled_class, exec_globals, locals())
|
||||
exec_globals[class_name] = locals()[class_name]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from langflow.core.celery_app import celery_app
|
|||
from langflow.processing.process import Result, generate_result, process_inputs
|
||||
from langflow.services.deps import get_session_service
|
||||
from langflow.services.manager import initialize_session_service
|
||||
from loguru import logger
|
||||
from rich import print
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
|
|
@ -36,19 +38,38 @@ def process_graph_cached_task(
|
|||
clear_cache=False,
|
||||
session_id=None,
|
||||
) -> Dict[str, Any]:
|
||||
initialize_session_service()
|
||||
session_service = get_session_service()
|
||||
if clear_cache:
|
||||
session_service.clear_session(session_id)
|
||||
if session_id is None:
|
||||
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
|
||||
# Load the graph using SessionService
|
||||
graph, artifacts = async_to_sync(session_service.load_session)(session_id, data_graph)
|
||||
built_object = graph.build()
|
||||
processed_inputs = process_inputs(inputs, artifacts)
|
||||
result = generate_result(built_object, processed_inputs)
|
||||
# langchain_object is now updated with the new memory
|
||||
# we need to update the cache with the updated langchain_object
|
||||
session_service.update_session(session_id, (graph, artifacts))
|
||||
try:
|
||||
initialize_session_service()
|
||||
session_service = get_session_service()
|
||||
|
||||
return Result(result=result, session_id=session_id).model_dump()
|
||||
if clear_cache:
|
||||
session_service.clear_session(session_id)
|
||||
|
||||
if session_id is None:
|
||||
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
|
||||
|
||||
# Use async_to_sync to handle the asynchronous part of the session service
|
||||
session_data = async_to_sync(session_service.load_session, force_new_loop=True)(session_id, data_graph)
|
||||
logger.warning(f"session_data: {session_data}")
|
||||
graph, artifacts = session_data if session_data else (None, None)
|
||||
|
||||
if not graph:
|
||||
raise ValueError("Graph not found in the session")
|
||||
|
||||
# Use async_to_sync for the asynchronous build method
|
||||
built_object = async_to_sync(graph.build, force_new_loop=True)()
|
||||
|
||||
logger.debug(f"Built object: {built_object}")
|
||||
|
||||
processed_inputs = process_inputs(inputs, artifacts or {})
|
||||
result = generate_result(built_object, processed_inputs)
|
||||
|
||||
# Update the session with the new data
|
||||
session_service.update_session(session_id, (graph, artifacts))
|
||||
result_object = Result(result=result, session_id=session_id).model_dump()
|
||||
print(f"Result object: {result_object}")
|
||||
return result_object
|
||||
except Exception as e:
|
||||
logger.error(f"Error in process_graph_cached_task: {e}")
|
||||
# Handle the exception as needed, maybe re-raise or return an error message
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const buttonVariants = cva(
|
|||
primary:
|
||||
"border bg-background text-secondary-foreground hover:bg-background/80 dark:hover:bg-background/10 hover:shadow-sm",
|
||||
secondary:
|
||||
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary/80",
|
||||
"border border-muted bg-muted text-muted-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "underline-offset-4 hover:underline text-primary",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
let text = await file.text();
|
||||
let fileData = JSON.parse(text);
|
||||
if (
|
||||
(fileData.is_component === undefined && isComponent === true) ||
|
||||
(!fileData.is_component && isComponent === true) ||
|
||||
(fileData.is_component !== undefined &&
|
||||
fileData.is_component !== isComponent)
|
||||
) {
|
||||
|
|
@ -339,11 +339,10 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
let fileData: FlowType = await JSON.parse(text);
|
||||
console.log(isComponent, fileData);
|
||||
|
||||
if (fileData.is_component === undefined) {
|
||||
reject("Your file doesn't have the is_component property.");
|
||||
} else if (
|
||||
fileData.is_component !== undefined &&
|
||||
fileData.is_component !== isComponent
|
||||
if (
|
||||
(!fileData.is_component && isComponent === true) ||
|
||||
(fileData.is_component !== undefined &&
|
||||
fileData.is_component !== isComponent)
|
||||
) {
|
||||
reject("You cannot upload a component as a flow or vice versa");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { InfinityIcon } from "lucide-react";
|
||||
import { InfinityIcon, Terminal, Code } from "lucide-react";
|
||||
import { forwardRef } from "react";
|
||||
import ForwardedIconComponent from "../../components/genericIconComponent";
|
||||
|
||||
export const GradientInfinity = forwardRef<
|
||||
SVGSVGElement,
|
||||
|
|
@ -15,7 +16,52 @@ export const GradientInfinity = forwardRef<
|
|||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<InfinityIcon stroke="url(#grad1)" ref={ref} {...props} />
|
||||
<Code stroke="url(#grad1)" ref={ref} {...props} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
export const GradientSave = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<>
|
||||
<svg width="0" height="0" style={{ position: "absolute" }}>
|
||||
<defs>
|
||||
<linearGradient id="grad2" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop className="gradient-start" offset="0%" />
|
||||
<stop className="gradient-end" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<ForwardedIconComponent
|
||||
name="Save"
|
||||
stroke="url(#grad2)"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const GradientGroup = (props) => {
|
||||
return (
|
||||
<>
|
||||
<svg width="0" height="0" style={{ position: "absolute" }}>
|
||||
<defs>
|
||||
<linearGradient id="grad3" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop className="gradient-start" offset="0%" />
|
||||
<stop className="gradient-end" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<ForwardedIconComponent
|
||||
name="Combine"
|
||||
stroke="url(#grad3)"
|
||||
{...props}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ function ConfirmationModal({
|
|||
cancelText,
|
||||
confirmationText,
|
||||
children,
|
||||
destructive = false,
|
||||
icon,
|
||||
data,
|
||||
index,
|
||||
|
|
@ -56,9 +57,9 @@ function ConfirmationModal({
|
|||
);
|
||||
|
||||
return (
|
||||
<BaseModal size={size ?? "x-small"} open={modalOpen} setOpen={setModalOpen}>
|
||||
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
|
||||
<BaseModal.Trigger asChild={asChild}>{triggerChild}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={titleHeader}>
|
||||
<BaseModal.Header description={titleHeader ?? null}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
|
|
@ -67,7 +68,7 @@ function ConfirmationModal({
|
|||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
{modalContentTitle != "" && (
|
||||
{modalContentTitle && modalContentTitle != "" && (
|
||||
<>
|
||||
<strong>{modalContentTitle}</strong>
|
||||
<br></br>
|
||||
|
|
@ -78,7 +79,8 @@ function ConfirmationModal({
|
|||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
className="ml-3 mt-5"
|
||||
className="ml-3"
|
||||
variant={destructive ? "destructive" : "default"}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
onConfirm(index, data);
|
||||
|
|
@ -88,7 +90,7 @@ function ConfirmationModal({
|
|||
</Button>
|
||||
|
||||
<Button
|
||||
className="mt-5"
|
||||
className=""
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (onCancel) onCancel();
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default function DeleteConfirmationModal({
|
|||
</span>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button className="mr-3">Cancel</Button>
|
||||
<Button className="mr-3" variant="outline">Cancel</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ function BaseModal({
|
|||
switch (size) {
|
||||
case "x-small":
|
||||
minWidth = "min-w-[20vw]";
|
||||
height = "h-[10vh]";
|
||||
height = " ";
|
||||
break;
|
||||
case "smaller":
|
||||
minWidth = "min-w-[40vw]";
|
||||
|
|
@ -150,7 +150,7 @@ function BaseModal({
|
|||
<div className="truncate-doubleline word-break-break-word">
|
||||
{headerChild}
|
||||
</div>
|
||||
<div className={`mt-2 flex flex-col ${height!} w-full `}>
|
||||
<div className={`flex flex-col ${height!} w-full `}>
|
||||
{ContentChild}
|
||||
</div>
|
||||
{ContentFooter && (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { NodeToolbar } from "reactflow";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import { GradientGroup } from "../../../../icons/GradientSparkles";
|
||||
export default function SelectionMenu({ onClick, nodes, isVisible }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
|
|
@ -37,15 +38,15 @@ export default function SelectionMenu({ onClick, nodes, isVisible }) {
|
|||
<div className="h-10 w-28 overflow-hidden">
|
||||
<div
|
||||
className={
|
||||
"h-10 w-24 rounded-md border border-indigo-300 bg-white px-2.5 text-gray-700 shadow-inner transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300" +
|
||||
(isTransitioning ? " translate-y-0" : " translate-y-10")
|
||||
"h-10 w-24 rounded-md border border-indigo-300 bg-white px-2.5 text-gray-700 shadow-inner transition-all duration-400 ease-in-out dark:bg-gray-800 dark:text-gray-300" +
|
||||
(isTransitioning ? " opacity-100" : " opacity-0 ")
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="flex h-full w-full items-center justify-between text-sm hover:text-indigo-500"
|
||||
onClick={onClick}
|
||||
>
|
||||
<IconComponent name="Group" className="w-6" />
|
||||
<GradientGroup strokeWidth={1.5} size={22} className="text-primary" />
|
||||
Group
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -343,7 +343,11 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
return -1;
|
||||
} else if (b.toLowerCase() === "saved_components") {
|
||||
return 1;
|
||||
} else {
|
||||
} else if (a.toLowerCase() === "custom_components") {
|
||||
return -2
|
||||
} else if (b.toLowerCase() === "custom_components") {
|
||||
return 2
|
||||
}else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -277,11 +277,10 @@ export default function NodeToolbarComponent({
|
|||
<ConfirmationModal
|
||||
asChild
|
||||
open={showOverrideModal}
|
||||
title={`Replace ${data.node?.display_name}`}
|
||||
titleHeader={`Please, confirm your save actions`}
|
||||
modalContentTitle="Attention!"
|
||||
cancelText="New"
|
||||
title={`Replace`}
|
||||
cancelText="Create New"
|
||||
confirmationText="Replace"
|
||||
size={"x-small"}
|
||||
icon={"SaveAll"}
|
||||
index={6}
|
||||
onConfirm={(index, user) => {
|
||||
|
|
@ -292,8 +291,7 @@ export default function NodeToolbarComponent({
|
|||
>
|
||||
<ConfirmationModal.Content>
|
||||
<span>
|
||||
It seems {data.node?.display_name} already exists. Replacing it
|
||||
will switch the current component. Proceed with replacement?
|
||||
It seems {data.node?.display_name} already exists. Do you want to replace it with the current or create a new one?
|
||||
</span>
|
||||
</ConfirmationModal.Content>
|
||||
<ConfirmationModal.Trigger>
|
||||
|
|
|
|||
|
|
@ -95,47 +95,49 @@ export default function ComponentsComponent({
|
|||
>
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
|
||||
{!isLoading || data?.length > 0 ? (
|
||||
data?.map((item, idx) => (
|
||||
<CollectionCardComponent
|
||||
onDelete={() => {
|
||||
removeFlow(item.id);
|
||||
}}
|
||||
key={idx}
|
||||
data={item}
|
||||
disabled={isLoading}
|
||||
button={
|
||||
!is_component ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap "
|
||||
onClick={() => {
|
||||
navigate("/flow/" + item.id);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="ExternalLink"
|
||||
className="main-page-nav-button"
|
||||
/>
|
||||
Edit Flow
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : !isLoading && data?.length === 0 ? (
|
||||
<>You haven't created {name}s yet.</>
|
||||
) : (
|
||||
<>
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!isLoading && data.length === 0 ? (
|
||||
<div className="w-full text-center">You haven't created any {name}s yet.</div>
|
||||
) : (
|
||||
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
|
||||
{!isLoading || data?.length > 0 ? (
|
||||
data?.map((item, idx) => (
|
||||
<CollectionCardComponent
|
||||
onDelete={() => {
|
||||
removeFlow(item.id);
|
||||
}}
|
||||
key={idx}
|
||||
data={item}
|
||||
disabled={isLoading}
|
||||
button={
|
||||
!is_component ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap "
|
||||
onClick={() => {
|
||||
navigate("/flow/" + item.id);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="ExternalLink"
|
||||
className="main-page-nav-button"
|
||||
/>
|
||||
Edit Flow
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isLoading && allData.length > 0 && (
|
||||
<div className="relative py-6">
|
||||
|
|
|
|||
|
|
@ -283,9 +283,10 @@ export type PaginatorComponentType = {
|
|||
export type ConfirmationModalType = {
|
||||
onCancel?: () => void;
|
||||
title: string;
|
||||
titleHeader: string;
|
||||
titleHeader?: string;
|
||||
asChild?: boolean;
|
||||
modalContentTitle: string;
|
||||
destructive?: boolean;
|
||||
modalContentTitle?: string;
|
||||
cancelText: string;
|
||||
confirmationText: string;
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ import {
|
|||
X,
|
||||
XCircle,
|
||||
Zap,
|
||||
Combine,
|
||||
TerminalIcon
|
||||
} from "lucide-react";
|
||||
import { FaApple, FaGithub } from "react-icons/fa";
|
||||
import { AWSIcon } from "../icons/AWS";
|
||||
|
|
@ -111,7 +113,7 @@ import { EvernoteIcon } from "../icons/Evernote";
|
|||
import { FBIcon } from "../icons/FacebookMessenger";
|
||||
import { GitBookIcon } from "../icons/GitBook";
|
||||
import { GoogleIcon } from "../icons/Google";
|
||||
import { GradientInfinity } from "../icons/GradientSparkles";
|
||||
import { GradientInfinity, GradientSave } from "../icons/GradientSparkles";
|
||||
import { HuggingFaceIcon } from "../icons/HuggingFace";
|
||||
import { IFixIcon } from "../icons/IFixIt";
|
||||
import { MetaIcon } from "../icons/Meta";
|
||||
|
|
@ -260,7 +262,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
advanced: Laptop2,
|
||||
chat: MessageCircle,
|
||||
embeddings: Fingerprint,
|
||||
saved_components: Save,
|
||||
saved_components: GradientSave,
|
||||
documentloaders: Paperclip,
|
||||
vectorstores: Layers,
|
||||
toolkits: Hammer,
|
||||
|
|
@ -356,4 +358,6 @@ export const nodeIconsLucide: iconsType = {
|
|||
Link,
|
||||
ToyBrick,
|
||||
RefreshCcw,
|
||||
Combine,
|
||||
TerminalIcon,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue