Add LLM cache (#541)
✨ feat(main.py): call setup_llm_caching function on app startup
The `setup_llm_caching` function is added to `utils.py` to set up LLM
caching. The function is then called on app startup in `main.py` using
the `app.on_event("startup")` method. This improves the performance of
the application by caching LLM objects.
This commit is contained in:
commit
3287f45137
92 changed files with 4706 additions and 2082 deletions
1119
poetry.lock
generated
1119
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.1.7"
|
||||
version = "0.2.0"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Logspace <contact@logspace.ai>"]
|
||||
maintainers = [
|
||||
|
|
@ -23,14 +23,14 @@ langflow = "langflow.__main__:main"
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<3.12"
|
||||
fastapi = "^0.97.0"
|
||||
fastapi = "^0.98.0"
|
||||
uvicorn = "^0.22.0"
|
||||
beautifulsoup4 = "^4.11.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.208"
|
||||
langchain = "^0.0.211"
|
||||
openai = "^0.27.8"
|
||||
types-pyyaml = "^6.0.12.8"
|
||||
pandas = "^1.5.3"
|
||||
|
|
@ -51,12 +51,13 @@ tiktoken = "~0.4.0"
|
|||
wikipedia = "^1.4.0"
|
||||
langchain-serve = { version = ">0.0.39", optional = true }
|
||||
qdrant-client = "^1.2.0"
|
||||
websockets = "^11.0.3"
|
||||
websockets = "^10.3"
|
||||
weaviate-client = "^3.21.0"
|
||||
jina = "3.15.2"
|
||||
sentence-transformers = "^2.2.2"
|
||||
ctransformers = "^0.2.2"
|
||||
cohere = "^4.6.0"
|
||||
python-multipart = "^0.0.6"
|
||||
sqlmodel = "^0.0.8"
|
||||
faiss-cpu = "^1.7.4"
|
||||
anthropic = "^0.2.10"
|
||||
|
|
@ -64,6 +65,11 @@ orjson = "^3.9.1"
|
|||
multiprocess = "^0.70.14"
|
||||
cachetools = "^5.3.1"
|
||||
types-cachetools = "^5.3.0.5"
|
||||
appdirs = "^1.4.4"
|
||||
pinecone-client = "^2.2.2"
|
||||
supabase = "^1.0.3"
|
||||
pymongo = "^4.4.0"
|
||||
certifi = "^2023.5.7"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
|
@ -78,6 +84,7 @@ requests = "^2.28.0"
|
|||
pytest-cov = "^4.0.0"
|
||||
pandas-stubs = "^2.0.0.230412"
|
||||
types-pillow = "^9.5.0.2"
|
||||
types-appdirs = "^1.4.3.5"
|
||||
|
||||
|
||||
[tool.poetry.extras]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import json
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
HTTPException,
|
||||
|
|
@ -7,7 +6,7 @@ from fastapi import (
|
|||
status,
|
||||
)
|
||||
from fastapi.responses import StreamingResponse
|
||||
from langflow.api.v1.schemas import BuiltResponse, InitResponse
|
||||
from langflow.api.v1.schemas import BuiltResponse, InitResponse, StreamData
|
||||
|
||||
from langflow.chat.manager import ChatManager
|
||||
from langflow.graph.graph.base import Graph
|
||||
|
|
@ -71,30 +70,37 @@ async def stream_build(flow_id: str):
|
|||
"""Stream the build process based on stored flow data."""
|
||||
|
||||
async def event_stream(flow_id):
|
||||
final_response = json.dumps({"end_of_stream": True})
|
||||
final_response = {"end_of_stream": True}
|
||||
try:
|
||||
if flow_id not in flow_data_store:
|
||||
error_message = "Invalid session ID"
|
||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
||||
yield str(StreamData(event="error", data={"error": error_message}))
|
||||
return
|
||||
|
||||
graph_data = flow_data_store[flow_id].get("data")
|
||||
|
||||
if not graph_data:
|
||||
error_message = "No data provided"
|
||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
||||
yield str(StreamData(event="error", data={"error": error_message}))
|
||||
return
|
||||
|
||||
logger.debug("Building langchain object")
|
||||
graph = Graph.from_payload(graph_data)
|
||||
try:
|
||||
# Some error could happen when building the graph
|
||||
graph = Graph.from_payload(graph_data)
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
error_message = str(exc)
|
||||
yield str(StreamData(event="error", data={"error": error_message}))
|
||||
return
|
||||
|
||||
number_of_nodes = len(graph.nodes)
|
||||
for i, vertex in enumerate(graph.generator_build(), 1):
|
||||
try:
|
||||
log_dict = {
|
||||
"log": f"Building node {vertex.vertex_type}",
|
||||
"progress": round(i / number_of_nodes, 2),
|
||||
}
|
||||
yield f"data: {json.dumps(log_dict)}\n\n"
|
||||
yield str(StreamData(event="log", data=log_dict))
|
||||
vertex.build()
|
||||
params = vertex._built_object_repr()
|
||||
valid = True
|
||||
|
|
@ -105,21 +111,21 @@ async def stream_build(flow_id: str):
|
|||
params = str(exc)
|
||||
valid = False
|
||||
|
||||
response = json.dumps(
|
||||
{
|
||||
"valid": valid,
|
||||
"params": params,
|
||||
"id": vertex.id,
|
||||
}
|
||||
)
|
||||
yield f"data: {response}\n\n"
|
||||
response = {
|
||||
"valid": valid,
|
||||
"params": params,
|
||||
"id": vertex.id,
|
||||
"progress": round(i / number_of_nodes, 2),
|
||||
}
|
||||
|
||||
yield str(StreamData(event="message", data=response))
|
||||
|
||||
chat_manager.set_cache(flow_id, graph.build())
|
||||
except Exception as exc:
|
||||
logger.error("Error while building the flow: %s", exc)
|
||||
yield f"error: {json.dumps({'error': str(exc)})}\n\n"
|
||||
yield str(StreamData(event="error", data={"error": str(exc)}))
|
||||
finally:
|
||||
yield f"data: {final_response}\n\n"
|
||||
yield str(StreamData(event="message", data=final_response))
|
||||
|
||||
try:
|
||||
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
from typing import Optional
|
||||
from langflow.cache.utils import save_uploaded_file
|
||||
from langflow.database.models.flow import Flow
|
||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||
from langflow.utils.logger import logger
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||
|
||||
from langflow.api.v1.schemas import (
|
||||
PredictRequest,
|
||||
PredictResponse,
|
||||
ProcessResponse,
|
||||
UploadFileResponse,
|
||||
)
|
||||
|
||||
from langflow.interface.types import build_langchain_types_dict
|
||||
|
|
@ -22,14 +24,17 @@ def get_all():
|
|||
return build_langchain_types_dict()
|
||||
|
||||
|
||||
@router.post("/predict/{flow_id}", response_model=PredictResponse)
|
||||
async def predict_flow(
|
||||
predict_request: PredictRequest,
|
||||
# For backwards compatibility we will keep the old endpoint
|
||||
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
||||
@router.post("/process/{flow_id}", response_model=ProcessResponse)
|
||||
async def process_flow(
|
||||
flow_id: str,
|
||||
inputs: Optional[dict] = None,
|
||||
tweaks: Optional[dict] = None,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""
|
||||
Endpoint to process a message using the flow passed in the bearer token.
|
||||
Endpoint to process an input with a given flow_id.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
|
@ -40,15 +45,14 @@ async def predict_flow(
|
|||
if flow.data is None:
|
||||
raise ValueError(f"Flow {flow_id} has no data")
|
||||
graph_data = flow.data
|
||||
if predict_request.tweaks:
|
||||
if tweaks:
|
||||
try:
|
||||
graph_data = process_tweaks(graph_data, predict_request.tweaks)
|
||||
graph_data = process_tweaks(graph_data, tweaks)
|
||||
except Exception as exc:
|
||||
logger.error(f"Error processing tweaks: {exc}")
|
||||
response = process_graph_cached(graph_data, predict_request.message)
|
||||
return PredictResponse(
|
||||
result=response.get("result", ""),
|
||||
intermediate_steps=response.get("thought", ""),
|
||||
response = process_graph_cached(graph_data, inputs)
|
||||
return ProcessResponse(
|
||||
result=response,
|
||||
)
|
||||
except Exception as e:
|
||||
# Log stack trace
|
||||
|
|
@ -56,6 +60,21 @@ async def predict_flow(
|
|||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.post("/upload/{flow_id}", response_model=UploadFileResponse, status_code=201)
|
||||
async def create_upload_file(file: UploadFile, flow_id: str):
|
||||
# Cache file
|
||||
try:
|
||||
file_path = save_uploaded_file(file.file, folder_name=flow_id)
|
||||
|
||||
return UploadFileResponse(
|
||||
flowId=flow_id,
|
||||
file_path=file_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(f"Error saving file: {exc}")
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
# get endpoint to return version of langflow
|
||||
@router.get("/version")
|
||||
def get_version():
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from langflow.database.models.flow import FlowCreate, FlowRead
|
||||
from pydantic import BaseModel, Field, validator
|
||||
import json
|
||||
|
||||
|
||||
class GraphData(BaseModel):
|
||||
|
|
@ -19,34 +21,22 @@ class ExportedFlow(BaseModel):
|
|||
data: GraphData
|
||||
|
||||
|
||||
class PredictRequest(BaseModel):
|
||||
"""Predict request schema."""
|
||||
class InputRequest(BaseModel):
|
||||
input: dict
|
||||
|
||||
message: str
|
||||
|
||||
class TweaksRequest(BaseModel):
|
||||
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"message": "Hello, how are you?",
|
||||
"tweaks": {
|
||||
"dndnode_986363f0-4677-4035-9f38-74b94af5dd78": {
|
||||
"name": "A tool name",
|
||||
"description": "A tool description",
|
||||
},
|
||||
"dndnode_986363f0-4677-4035-9f38-74b94af57378": {
|
||||
"template": "A {template}",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateTemplateRequest(BaseModel):
|
||||
template: dict
|
||||
|
||||
|
||||
class PredictResponse(BaseModel):
|
||||
"""Predict response schema."""
|
||||
class ProcessResponse(BaseModel):
|
||||
"""Process response schema."""
|
||||
|
||||
result: str
|
||||
intermediate_steps: str = ""
|
||||
result: dict
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
|
|
@ -101,3 +91,18 @@ class InitResponse(BaseModel):
|
|||
|
||||
class BuiltResponse(BaseModel):
|
||||
built: bool
|
||||
|
||||
|
||||
class UploadFileResponse(BaseModel):
|
||||
"""Upload file response schema."""
|
||||
|
||||
flowId: str
|
||||
file_path: Path
|
||||
|
||||
|
||||
class StreamData(BaseModel):
|
||||
event: str
|
||||
data: dict
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import json
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from langflow.api.v1.base import (
|
||||
|
|
@ -9,8 +7,6 @@ from langflow.api.v1.base import (
|
|||
PromptValidationResponse,
|
||||
validate_prompt,
|
||||
)
|
||||
from langflow.graph.vertex.types import VectorStoreVertex
|
||||
from langflow.graph import Graph
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.validate import validate_code
|
||||
|
||||
|
|
@ -37,21 +33,3 @@ def post_validate_prompt(prompt: Prompt):
|
|||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
# validate node
|
||||
@router.post("/node/{node_id}", status_code=200)
|
||||
def post_validate_node(node_id: str, data: dict):
|
||||
try:
|
||||
# build graph
|
||||
graph = Graph.from_payload(data)
|
||||
# validate node
|
||||
node = graph.get_node(node_id)
|
||||
if node is None:
|
||||
raise ValueError(f"Node {node_id} not found")
|
||||
if not isinstance(node, VectorStoreVertex):
|
||||
node.build()
|
||||
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return json.dumps({"valid": False, "params": str(e)})
|
||||
|
|
|
|||
3
src/backend/langflow/cache/base.py
vendored
3
src/backend/langflow/cache/base.py
vendored
|
|
@ -62,9 +62,6 @@ class BaseCache(abc.ABC):
|
|||
|
||||
Args:
|
||||
key: The key of the item to retrieve.
|
||||
|
||||
Returns:
|
||||
The value associated with the key, or None if the key is not found.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
|
|
|||
51
src/backend/langflow/cache/utils.py
vendored
51
src/backend/langflow/cache/utils.py
vendored
|
|
@ -8,15 +8,17 @@ import tempfile
|
|||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from appdirs import user_cache_dir
|
||||
|
||||
CACHE: Dict[str, Any] = {}
|
||||
|
||||
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||
|
||||
|
||||
def create_cache_folder(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
# Get the destination folder
|
||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
||||
cache_path = Path(CACHE_DIR) / PREFIX
|
||||
|
||||
# Create the destination folder if it doesn't exist
|
||||
os.makedirs(cache_path, exist_ok=True)
|
||||
|
|
@ -118,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
|||
raise ValueError(f"File {file_name} is not accepted")
|
||||
|
||||
# Get the destination folder
|
||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
||||
cache_path = Path(CACHE_DIR) / PREFIX
|
||||
if not content:
|
||||
raise ValueError("Please, reload the file in the loader.")
|
||||
data = content.split(",")[1]
|
||||
|
|
@ -132,3 +134,46 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
|||
file.write(decoded_bytes)
|
||||
|
||||
return file_path
|
||||
|
||||
|
||||
@create_cache_folder
|
||||
def save_uploaded_file(file, folder_name):
|
||||
"""
|
||||
Save an uploaded file to the specified folder with a hash of its content as the file name.
|
||||
|
||||
Args:
|
||||
file: The uploaded file object.
|
||||
folder_name: The name of the folder to save the file in.
|
||||
|
||||
Returns:
|
||||
The path to the saved file.
|
||||
"""
|
||||
cache_path = Path(CACHE_DIR)
|
||||
folder_path = cache_path / folder_name
|
||||
|
||||
# Create the folder if it doesn't exist
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir()
|
||||
|
||||
# Create a hash of the file content
|
||||
sha256_hash = hashlib.sha256()
|
||||
# Reset the file cursor to the beginning of the file
|
||||
file.seek(0)
|
||||
# Iterate over the uploaded file in small chunks to conserve memory
|
||||
while chunk := file.read(8192): # Read 8KB at a time (adjust as needed)
|
||||
sha256_hash.update(chunk)
|
||||
|
||||
# Use the hex digest of the hash as the file name
|
||||
hex_dig = sha256_hash.hexdigest()
|
||||
file_name = hex_dig
|
||||
|
||||
# Reset the file cursor to the beginning of the file
|
||||
file.seek(0)
|
||||
|
||||
# Save the file with the hash as its name
|
||||
file_path = folder_path / file_name
|
||||
with open(file_path, "wb") as new_file:
|
||||
while chunk := file.read(8192):
|
||||
new_file.write(chunk)
|
||||
|
||||
return file_path
|
||||
|
|
|
|||
|
|
@ -144,6 +144,8 @@ class ChatManager:
|
|||
if isinstance(msg, FileResponse):
|
||||
if msg.data_type == "image":
|
||||
# Base64 encode the image
|
||||
if isinstance(msg.data, str):
|
||||
continue
|
||||
msg.data = pil_to_base64(msg.data)
|
||||
file_responses.append(msg)
|
||||
if msg.type == "start":
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ documentloaders:
|
|||
- SlackDirectoryLoader
|
||||
- NotionDirectoryLoader
|
||||
- DirectoryLoader
|
||||
- GitLoader
|
||||
embeddings:
|
||||
- OpenAIEmbeddings
|
||||
- HuggingFaceEmbeddings
|
||||
|
|
@ -58,7 +59,7 @@ llms:
|
|||
# - AzureOpenAI
|
||||
# - AzureChatOpenAI
|
||||
- ChatOpenAI
|
||||
- LlamaCpp
|
||||
- LlamaCpp
|
||||
- CTransformers
|
||||
- Cohere
|
||||
- Anthropic
|
||||
|
|
@ -130,6 +131,9 @@ vectorstores:
|
|||
- Qdrant
|
||||
- Weaviate
|
||||
- FAISS
|
||||
- Pinecone
|
||||
- SupabaseVectorStore
|
||||
- MongoDBAtlasVectorSearch
|
||||
wrappers:
|
||||
- RequestsWrapper
|
||||
# - ChatPromptTemplate
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class Graph:
|
|||
def generator_build(self) -> Generator:
|
||||
"""Builds each vertex in the graph and yields it."""
|
||||
sorted_vertices = self.topological_sort()
|
||||
logger.info("Sorted vertices: %s", sorted_vertices)
|
||||
logger.debug("Sorted vertices: %s", sorted_vertices)
|
||||
yield from sorted_vertices
|
||||
|
||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ from langflow.interface.wrappers.base import wrapper_creator
|
|||
from typing import Dict, Type
|
||||
|
||||
|
||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||
|
||||
|
||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from langflow.cache import utils as cache_utils
|
||||
from langflow.graph.vertex.constants import DIRECT_TYPES
|
||||
from langflow.interface import loading
|
||||
from langflow.utils.constants import DIRECT_TYPES
|
||||
from langflow.interface.initialize import loading
|
||||
from langflow.interface.listing import ALL_TYPES_DICT
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import sync_to_async
|
||||
|
|
@ -90,12 +89,7 @@ class Vertex:
|
|||
# Load the type in value.get('suffixes') using
|
||||
# what is inside value.get('content')
|
||||
# value.get('value') is the file name
|
||||
file_name = value.get("value")
|
||||
content = value.get("content")
|
||||
type_to_load = value.get("suffixes")
|
||||
file_path = cache_utils.save_binary_file(
|
||||
content=content, file_name=file_name, accepted_types=type_to_load
|
||||
)
|
||||
file_path = value.get("file_path")
|
||||
|
||||
params[key] = file_path
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||
|
||||
|
|
|
|||
|
|
@ -106,9 +106,6 @@ class VectorStoreVertex(Vertex):
|
|||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="vectorstores")
|
||||
|
||||
def _built_object_repr(self):
|
||||
return "Vector stores can take time to build. It will build on the first query."
|
||||
|
||||
|
||||
class MemoryVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
|||
value=value.get("value", None),
|
||||
suffixes=value.get("suffixes", []),
|
||||
file_types=value.get("fileTypes", []),
|
||||
content=value.get("content", None),
|
||||
file_path=value.get("file_path", None),
|
||||
)
|
||||
for key, value in signature["template"].items()
|
||||
if key != "_type"
|
||||
|
|
|
|||
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
|
|
@ -1,31 +1,21 @@
|
|||
import json
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
from typing import Any, Callable, Dict, Sequence
|
||||
|
||||
from langchain.agents import ZeroShotAgent
|
||||
from langchain.agents import agent as agent_module
|
||||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
)
|
||||
from langchain.agents.loading import load_agent_from_config
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.callbacks.base import BaseCallbackManager
|
||||
from langchain.chains.loading import load_chain_from_config
|
||||
from langchain.llms.loading import load_llm_from_config
|
||||
from langchain.agents.tools import BaseTool
|
||||
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||
from langflow.interface.importing.utils import get_function, import_by_type
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
from langflow.interface.types import get_type_list
|
||||
from langflow.interface.utils import load_file_into_dict
|
||||
from langflow.utils import util, validate
|
||||
from langflow.utils import validate
|
||||
|
||||
|
||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||
|
|
@ -153,24 +143,48 @@ def instantiate_embedding(class_object, params):
|
|||
|
||||
|
||||
def instantiate_vectorstore(class_object, params):
|
||||
if len(params.get("documents", [])) == 0:
|
||||
raise ValueError(
|
||||
"The source you provided did not load correctly or was empty."
|
||||
"This may cause an error in the vectorstore."
|
||||
)
|
||||
# Chroma requires all metadata values to not be None
|
||||
if class_object.__name__ == "Chroma":
|
||||
for doc in params["documents"]:
|
||||
if doc.metadata is None:
|
||||
doc.metadata = {}
|
||||
for key, value in doc.metadata.items():
|
||||
if value is None:
|
||||
doc.metadata[key] = ""
|
||||
return class_object.from_documents(**params)
|
||||
search_kwargs = params.pop("search_kwargs", {})
|
||||
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||
vecstore = initializer(class_object, params)
|
||||
else:
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
vecstore = class_object.from_documents(**params)
|
||||
|
||||
# ! This might not work. Need to test
|
||||
if search_kwargs and hasattr(vecstore, "as_retriever"):
|
||||
vecstore = vecstore.as_retriever(search_kwargs=search_kwargs)
|
||||
|
||||
return vecstore
|
||||
|
||||
|
||||
def instantiate_documentloader(class_object, params):
|
||||
return class_object(**params).load()
|
||||
if "file_filter" in params:
|
||||
# file_filter will be a string but we need a function
|
||||
# that will be used to filter the files using file_filter
|
||||
# like lambda x: x.endswith(".txt") but as we don't know
|
||||
# anything besides the string, we will simply check if the string is
|
||||
# in x and if it is, we will return True
|
||||
file_filter = params.pop("file_filter", None)
|
||||
extensions = file_filter.split(",")
|
||||
params["file_filter"] = lambda x: any(
|
||||
extension.strip() in x for extension in extensions
|
||||
)
|
||||
metadata = params.pop("metadata", None)
|
||||
docs = class_object(**params).load()
|
||||
if metadata:
|
||||
if isinstance(metadata, str):
|
||||
try:
|
||||
metadata = json.loads(metadata)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
"The metadata you provided is not a valid JSON string."
|
||||
) from exc
|
||||
|
||||
for doc in docs:
|
||||
doc.metadata = metadata
|
||||
|
||||
return docs
|
||||
|
||||
|
||||
def instantiate_textsplitter(class_object, params):
|
||||
|
|
@ -207,48 +221,14 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
|||
return nodes
|
||||
|
||||
|
||||
def load_langchain_type_from_config(config: Dict[str, Any]):
|
||||
"""Load langchain type from config"""
|
||||
# Get type list
|
||||
type_list = get_type_list()
|
||||
if config["_type"] in type_list["agents"]:
|
||||
config = util.update_verbose(config, new_value=False)
|
||||
return load_agent_executor_from_config(config, verbose=True)
|
||||
elif config["_type"] in type_list["chains"]:
|
||||
config = util.update_verbose(config, new_value=False)
|
||||
return load_chain_from_config(config, verbose=True)
|
||||
elif config["_type"] in type_list["llms"]:
|
||||
config = util.update_verbose(config, new_value=True)
|
||||
return load_llm_from_config(config)
|
||||
else:
|
||||
raise ValueError("Type should be either agent, chain or llm")
|
||||
|
||||
|
||||
def load_agent_executor_from_config(
|
||||
config: dict,
|
||||
llm: Optional[BaseLanguageModel] = None,
|
||||
tools: Optional[list[Tool]] = None,
|
||||
callback_manager: Optional[BaseCallbackManager] = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
tools = load_tools_from_config(config["allowed_tools"])
|
||||
config["allowed_tools"] = [tool.name for tool in tools] if tools else []
|
||||
agent_obj = load_agent_from_config(config, llm, tools, **kwargs)
|
||||
|
||||
return AgentExecutor.from_agent_and_tools(
|
||||
agent=agent_obj,
|
||||
tools=tools,
|
||||
callback_manager=callback_manager,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
||||
"""Load agent executor from agent class, tools and chain"""
|
||||
allowed_tools = params.get("allowed_tools", [])
|
||||
allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", [])
|
||||
llm_chain = params["llm_chain"]
|
||||
# if allowed_tools is not a list or set, make it a list
|
||||
if not isinstance(allowed_tools, (list, set)):
|
||||
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
||||
allowed_tools, BaseTool
|
||||
):
|
||||
allowed_tools = [allowed_tools]
|
||||
tool_names = [tool.name for tool in allowed_tools]
|
||||
# Agent class requires an output_parser but Agent classes
|
||||
|
|
@ -267,46 +247,6 @@ def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict):
|
|||
return create_function(llm=llm, toolkit=toolkit)
|
||||
|
||||
|
||||
def load_tools_from_config(tool_list: list[dict]) -> list:
|
||||
"""Load tools based on a config list.
|
||||
|
||||
Args:
|
||||
config: config list.
|
||||
|
||||
Returns:
|
||||
List of tools.
|
||||
"""
|
||||
tools = []
|
||||
for tool in tool_list:
|
||||
tool_type = tool.pop("_type")
|
||||
llm_config = tool.pop("llm", None)
|
||||
llm = load_llm_from_config(llm_config) if llm_config else None
|
||||
kwargs = tool
|
||||
if tool_type in _BASE_TOOLS:
|
||||
tools.append(_BASE_TOOLS[tool_type]())
|
||||
elif tool_type in _LLM_TOOLS:
|
||||
if llm is None:
|
||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
||||
tools.append(_LLM_TOOLS[tool_type](llm))
|
||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
||||
if llm is None:
|
||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
||||
_get_llm_tool_func, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
||||
if missing_keys := set(extra_keys).difference(kwargs):
|
||||
raise ValueError(
|
||||
f"Tool {tool_type} requires some parameters that were not "
|
||||
f"provided: {missing_keys}"
|
||||
)
|
||||
tools.append(_get_llm_tool_func(llm=llm, **kwargs))
|
||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
||||
_get_tool_func, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
||||
kwargs = {k: value for k, value in kwargs.items() if value}
|
||||
tools.append(_get_tool_func(**kwargs))
|
||||
else:
|
||||
raise ValueError(f"Got unknown tool {tool_type}")
|
||||
return tools
|
||||
|
||||
|
||||
def build_prompt_template(prompt, tools):
|
||||
"""Build PromptTemplate from ZeroShotPrompt"""
|
||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import json
|
||||
from typing import Any, Callable, Dict, Type
|
||||
from langchain.vectorstores import (
|
||||
Pinecone,
|
||||
Qdrant,
|
||||
Chroma,
|
||||
FAISS,
|
||||
Weaviate,
|
||||
SupabaseVectorStore,
|
||||
MongoDBAtlasVectorSearch,
|
||||
)
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def docs_in_params(params: dict) -> bool:
|
||||
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||
If any of them is not an empty list, return True, else return False"""
|
||||
return ("documents" in params and params["documents"]) or (
|
||||
"texts" in params and params["texts"]
|
||||
)
|
||||
|
||||
|
||||
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||
"""Initialize mongodb and return the class object"""
|
||||
|
||||
MONGODB_ATLAS_CLUSTER_URI = params.pop("mongodb_atlas_cluster_uri")
|
||||
if not MONGODB_ATLAS_CLUSTER_URI:
|
||||
raise ValueError("Mongodb atlas cluster uri must be provided in the params")
|
||||
from pymongo import MongoClient
|
||||
import certifi
|
||||
|
||||
client: MongoClient = MongoClient(
|
||||
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
||||
)
|
||||
db_name = params.pop("db_name", None)
|
||||
collection_name = params.pop("collection_name", None)
|
||||
if not db_name or not collection_name:
|
||||
raise ValueError("db_name and collection_name must be provided in the params")
|
||||
|
||||
index_name = params.pop("index_name", None)
|
||||
if not index_name:
|
||||
raise ValueError("index_name must be provided in the params")
|
||||
|
||||
collection = client[db_name][collection_name]
|
||||
if not docs_in_params(params):
|
||||
# __init__ requires collection, embedding and index_name
|
||||
init_args = {
|
||||
"collection": collection,
|
||||
"index_name": index_name,
|
||||
"embedding": params.get("embedding"),
|
||||
}
|
||||
|
||||
return class_object(**init_args)
|
||||
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
|
||||
params["collection"] = collection
|
||||
params["index_name"] = index_name
|
||||
|
||||
return class_object.from_documents(**params)
|
||||
|
||||
|
||||
def initialize_supabase(class_object: Type[SupabaseVectorStore], params: dict):
|
||||
"""Initialize supabase and return the class object"""
|
||||
from supabase.client import Client, create_client
|
||||
|
||||
if "supabase_url" not in params or "supabase_service_key" not in params:
|
||||
raise ValueError("Supabase url and service key must be provided in the params")
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
|
||||
client_kwargs = {
|
||||
"supabase_url": params.pop("supabase_url"),
|
||||
"supabase_key": params.pop("supabase_service_key"),
|
||||
}
|
||||
|
||||
supabase: Client = create_client(**client_kwargs)
|
||||
if not docs_in_params(params):
|
||||
params.pop("documents", None)
|
||||
params.pop("texts", None)
|
||||
return class_object(client=supabase, **params)
|
||||
# If there are docs in the params, create a new index
|
||||
|
||||
return class_object.from_documents(client=supabase, **params)
|
||||
|
||||
|
||||
def initialize_weaviate(class_object: Type[Weaviate], params: dict):
|
||||
"""Initialize weaviate and return the class object"""
|
||||
if not docs_in_params(params):
|
||||
import weaviate # type: ignore
|
||||
|
||||
client_kwargs_json = params.get("client_kwargs", "{}")
|
||||
client_kwargs = json.loads(client_kwargs_json)
|
||||
client_params = {
|
||||
"url": params.get("weaviate_url"),
|
||||
}
|
||||
client_params.update(client_kwargs)
|
||||
weaviate_client = weaviate.Client(**client_params)
|
||||
|
||||
new_params = {
|
||||
"client": weaviate_client,
|
||||
"index_name": params.get("index_name"),
|
||||
"text_key": params.get("text_key"),
|
||||
}
|
||||
return class_object(**new_params)
|
||||
# If there are docs in the params, create a new index
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
|
||||
return class_object.from_documents(**params)
|
||||
|
||||
|
||||
def initialize_faiss(class_object: Type[FAISS], params: dict):
|
||||
"""Initialize faiss and return the class object"""
|
||||
|
||||
if not docs_in_params(params):
|
||||
return class_object.load_local
|
||||
|
||||
save_local = params.get("save_local")
|
||||
faiss_index = class_object(**params)
|
||||
if save_local:
|
||||
faiss_index.save_local(folder_path=save_local)
|
||||
return faiss_index
|
||||
|
||||
|
||||
def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||
"""Initialize pinecone and return the class object"""
|
||||
|
||||
import pinecone # type: ignore
|
||||
|
||||
pinecone_api_key = params.get("pinecone_api_key")
|
||||
pinecone_env = params.get("pinecone_env")
|
||||
|
||||
if pinecone_api_key is None or pinecone_env is None:
|
||||
if os.getenv("PINECONE_API_KEY") is not None:
|
||||
pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
||||
if os.getenv("PINECONE_ENV") is not None:
|
||||
pinecone_env = os.getenv("PINECONE_ENV")
|
||||
|
||||
if pinecone_api_key is None or pinecone_env is None:
|
||||
raise ValueError(
|
||||
"Pinecone API key and environment must be provided in the params"
|
||||
)
|
||||
|
||||
# initialize pinecone
|
||||
pinecone.init(
|
||||
api_key=pinecone_api_key, # find at app.pinecone.io
|
||||
environment=pinecone_env, # next to api key in console
|
||||
)
|
||||
|
||||
# If there are no docs in the params, return an existing index
|
||||
# but first remove any texts or docs keys from the params
|
||||
if not docs_in_params(params):
|
||||
existing_index_params = {
|
||||
"embedding": params.pop("embedding"),
|
||||
}
|
||||
if "index_name" in params:
|
||||
existing_index_params["index_name"] = params.pop("index_name")
|
||||
if "namespace" in params:
|
||||
existing_index_params["namespace"] = params.pop("namespace")
|
||||
|
||||
return class_object.from_existing_index(**existing_index_params)
|
||||
# If there are docs in the params, create a new index
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
return class_object.from_documents(**params)
|
||||
|
||||
|
||||
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||
"""Initialize a ChromaDB object from the params"""
|
||||
persist = params.pop("persist", False)
|
||||
if not docs_in_params(params):
|
||||
params.pop("documents", None)
|
||||
params.pop("texts", None)
|
||||
params["embedding_function"] = params.pop("embedding")
|
||||
chromadb = class_object(**params)
|
||||
else:
|
||||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
for doc in params["documents"]:
|
||||
if doc.metadata is None:
|
||||
doc.metadata = {}
|
||||
for key, value in doc.metadata.items():
|
||||
if value is None:
|
||||
doc.metadata[key] = ""
|
||||
chromadb = class_object.from_documents(**params)
|
||||
if persist:
|
||||
chromadb.persist()
|
||||
return chromadb
|
||||
|
||||
|
||||
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
||||
if not docs_in_params(params):
|
||||
if "location" not in params and "api_key" not in params:
|
||||
raise ValueError("Location and API key must be provided in the params")
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
client_params = {
|
||||
"location": params.pop("location"),
|
||||
"api_key": params.pop("api_key"),
|
||||
}
|
||||
lc_params = {
|
||||
"collection_name": params.pop("collection_name"),
|
||||
"embeddings": params.pop("embedding"),
|
||||
}
|
||||
client = QdrantClient(**client_params)
|
||||
|
||||
return class_object(client=client, **lc_params)
|
||||
|
||||
return class_object.from_documents(**params)
|
||||
|
||||
|
||||
vecstore_initializer: Dict[str, Callable[[Type[Any], dict], Any]] = {
|
||||
"Pinecone": initialize_pinecone,
|
||||
"Chroma": initialize_chroma,
|
||||
"Qdrant": initialize_qdrant,
|
||||
"Weaviate": initialize_weaviate,
|
||||
"FAISS": initialize_faiss,
|
||||
"SupabaseVectorStore": initialize_supabase,
|
||||
"MongoDBAtlasVectorSearch": initialize_mongodb,
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import re
|
|||
import yaml
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from PIL.Image import Image
|
||||
from langflow.utils.logger import logger
|
||||
|
||||
|
||||
def load_file_into_dict(file_path: str) -> dict:
|
||||
|
|
@ -58,3 +59,12 @@ def try_setting_streaming_options(langchain_object, websocket):
|
|||
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||
"""Extract input variables from prompt."""
|
||||
return re.findall(r"{(.*?)}", prompt)
|
||||
|
||||
|
||||
def setup_llm_caching():
|
||||
"""Setup LLM caching."""
|
||||
import langchain
|
||||
from langchain.cache import SQLiteCache
|
||||
|
||||
logger.debug("Setting up LLM caching")
|
||||
langchain.llm_cache = SQLiteCache()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||
|
||||
from langflow.api import router
|
||||
from langflow.database.base import create_db_and_tables
|
||||
from langflow.interface.utils import setup_llm_caching
|
||||
|
||||
|
||||
def create_app():
|
||||
|
|
@ -28,6 +29,7 @@ def create_app():
|
|||
|
||||
app.include_router(router)
|
||||
app.on_event("startup")(create_db_and_tables)
|
||||
app.on_event("startup")(setup_llm_caching)
|
||||
return app
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import contextlib
|
||||
import io
|
||||
from pathlib import Path
|
||||
from langchain.schema import AgentAction
|
||||
import json
|
||||
|
|
@ -10,7 +8,8 @@ from langflow.interface.run import (
|
|||
)
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.graph import Graph
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
|
||||
|
|
@ -55,69 +54,42 @@ def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
|||
return "\n".join(output)
|
||||
|
||||
|
||||
def get_result_and_thought(langchain_object, message: str):
|
||||
def get_result_and_thought(langchain_object: Any, inputs: dict):
|
||||
"""Get result and thought from extracted json"""
|
||||
try:
|
||||
if hasattr(langchain_object, "verbose"):
|
||||
langchain_object.verbose = True
|
||||
chat_input = None
|
||||
memory_key = ""
|
||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
||||
memory_key = langchain_object.memory.memory_key
|
||||
|
||||
if hasattr(langchain_object, "input_keys"):
|
||||
for key in langchain_object.input_keys:
|
||||
if key not in [memory_key, "chat_history"]:
|
||||
chat_input = {key: message}
|
||||
else:
|
||||
chat_input = message # type: ignore
|
||||
|
||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||
# https://github.com/hwchase17/langchain/issues/2068
|
||||
# Deactivating until we have a frontend solution
|
||||
# to display intermediate steps
|
||||
langchain_object.return_intermediate_steps = False
|
||||
langchain_object.return_intermediate_steps = True
|
||||
|
||||
fix_memory_inputs(langchain_object)
|
||||
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
try:
|
||||
# if hasattr(langchain_object, "acall"):
|
||||
# output = await langchain_object.acall(chat_input)
|
||||
# else:
|
||||
output = langchain_object(chat_input)
|
||||
except ValueError as exc:
|
||||
# make the error message more informative
|
||||
logger.debug(f"Error: {str(exc)}")
|
||||
output = langchain_object.run(chat_input)
|
||||
|
||||
intermediate_steps = (
|
||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||
)
|
||||
|
||||
result = (
|
||||
output.get(langchain_object.output_keys[0])
|
||||
if isinstance(output, dict)
|
||||
else output
|
||||
)
|
||||
if intermediate_steps:
|
||||
thought = format_actions(intermediate_steps)
|
||||
else:
|
||||
thought = output_buffer.getvalue()
|
||||
try:
|
||||
output = langchain_object(inputs, return_only_outputs=True)
|
||||
except ValueError as exc:
|
||||
# make the error message more informative
|
||||
logger.debug(f"Error: {str(exc)}")
|
||||
output = langchain_object.run(inputs)
|
||||
|
||||
except Exception as exc:
|
||||
raise ValueError(f"Error: {str(exc)}") from exc
|
||||
return result, thought
|
||||
return output
|
||||
|
||||
|
||||
def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
||||
def get_input_str_if_only_one_input(inputs: dict) -> Optional[str]:
|
||||
"""Get input string if only one input is provided"""
|
||||
return list(inputs.values())[0] if len(inputs) == 1 else None
|
||||
|
||||
|
||||
def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = None):
|
||||
"""
|
||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||
with PromptTemplate,then run the graph and return the result and thought.
|
||||
"""
|
||||
# Load langchain object
|
||||
langchain_object = build_langchain_object_with_caching(data_graph)
|
||||
logger.debug("Loaded langchain object")
|
||||
logger.debug("Loaded LangChain object")
|
||||
|
||||
if langchain_object is None:
|
||||
# Raise user facing error
|
||||
|
|
@ -126,10 +98,16 @@ def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
|||
)
|
||||
|
||||
# Generate result and thought
|
||||
logger.debug("Generating result and thought")
|
||||
result, thought = get_result_and_thought(langchain_object, message)
|
||||
logger.debug("Generated result and thought")
|
||||
return {"result": str(result), "thought": thought.strip()}
|
||||
if isinstance(langchain_object, Chain):
|
||||
if inputs is None:
|
||||
raise ValueError("Inputs must be provided for a Chain")
|
||||
logger.debug("Generating result and thought")
|
||||
result = get_result_and_thought(langchain_object, inputs)
|
||||
logger.debug("Generated result and thought")
|
||||
elif isinstance(langchain_object, VectorStore):
|
||||
class_name = langchain_object.__class__.__name__
|
||||
result = {"message": f"Processed {class_name} successfully"}
|
||||
return result
|
||||
|
||||
|
||||
def load_flow_from_json(
|
||||
|
|
@ -206,7 +184,8 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
|||
|
||||
for tweak_name, tweak_value in node_tweaks.items():
|
||||
if tweak_name and tweak_value and tweak_name in template_data:
|
||||
template_data[tweak_name]["value"] = tweak_value
|
||||
key = tweak_name if tweak_name == "file_path" else "value"
|
||||
template_data[tweak_name][key] = tweak_value
|
||||
|
||||
|
||||
def process_tweaks(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
|||
suffixes: list[str] = []
|
||||
fileTypes: list[str] = []
|
||||
file_types: list[str] = []
|
||||
content: Union[str, None] = None
|
||||
file_path: Union[str, None] = None
|
||||
password: bool = False
|
||||
options: list[str] = []
|
||||
name: str = ""
|
||||
|
|
@ -35,7 +35,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
|||
result["fileTypes"] = result.pop("file_types")
|
||||
|
||||
if self.field_type == "file":
|
||||
result["content"] = self.content
|
||||
result["file_path"] = self.file_path
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,37 @@ class ChainFrontendNode(FrontendNode):
|
|||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="BaseChatMemory",
|
||||
required=False,
|
||||
required=True,
|
||||
show=True,
|
||||
name="memory",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
# add return_source_documents
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="bool",
|
||||
required=False,
|
||||
show=True,
|
||||
name="return_source_documents",
|
||||
advanced=False,
|
||||
value=True,
|
||||
display_name="Return source documents",
|
||||
)
|
||||
)
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=True,
|
||||
is_list=True,
|
||||
show=True,
|
||||
multiline=False,
|
||||
options=QA_CHAIN_TYPES,
|
||||
value=QA_CHAIN_TYPES[0],
|
||||
name="chain_type",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
|
|
@ -47,7 +72,7 @@ class ChainFrontendNode(FrontendNode):
|
|||
field.show = True
|
||||
field.advanced = False
|
||||
if field.name == "memory":
|
||||
field.required = False
|
||||
# field.required = False
|
||||
field.show = True
|
||||
field.advanced = False
|
||||
if field.name == "verbose":
|
||||
|
|
@ -59,6 +84,12 @@ class ChainFrontendNode(FrontendNode):
|
|||
field.show = True
|
||||
field.advanced = False
|
||||
|
||||
if field.name == "return_source_documents":
|
||||
field.required = False
|
||||
field.show = True
|
||||
field.advanced = True
|
||||
field.value = True
|
||||
|
||||
|
||||
class SeriesCharacterChainNode(FrontendNode):
|
||||
name: str = "SeriesCharacterChain"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from typing import Optional
|
||||
from langflow.template.field.base import TemplateField
|
||||
from langflow.template.frontend_node.base import FrontendNode
|
||||
|
||||
|
||||
def build_template(
|
||||
def build_file_field(
|
||||
suffixes: list, fileTypes: list, name: str = "file_path"
|
||||
) -> TemplateField:
|
||||
"""Build a template field for a document loader."""
|
||||
|
|
@ -19,32 +20,35 @@ def build_template(
|
|||
|
||||
class DocumentLoaderFrontNode(FrontendNode):
|
||||
file_path_templates = {
|
||||
"AirbyteJSONLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
||||
"CoNLLULoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
||||
"CSVLoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
||||
"UnstructuredEmailLoader": build_template(suffixes=[".eml"], fileTypes=["eml"]),
|
||||
"EverNoteLoader": build_template(suffixes=[".xml"], fileTypes=["xml"]),
|
||||
"FacebookChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
||||
"GutenbergLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
||||
"BSHTMLLoader": build_template(suffixes=[".html"], fileTypes=["html"]),
|
||||
"UnstructuredHTMLLoader": build_template(
|
||||
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||
"UnstructuredEmailLoader": build_file_field(
|
||||
suffixes=[".eml"], fileTypes=["eml"]
|
||||
),
|
||||
"SlackDirectoryLoader": build_file_field(suffixes=[".zip"], fileTypes=["zip"]),
|
||||
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
||||
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||
"GutenbergLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||
"UnstructuredHTMLLoader": build_file_field(
|
||||
suffixes=[".html"], fileTypes=["html"]
|
||||
),
|
||||
"UnstructuredImageLoader": build_template(
|
||||
"UnstructuredImageLoader": build_file_field(
|
||||
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
||||
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
||||
),
|
||||
"UnstructuredMarkdownLoader": build_template(
|
||||
"UnstructuredMarkdownLoader": build_file_field(
|
||||
suffixes=[".md"], fileTypes=["md"]
|
||||
),
|
||||
"PyPDFLoader": build_template(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||
"UnstructuredPowerPointLoader": build_template(
|
||||
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||
"UnstructuredPowerPointLoader": build_file_field(
|
||||
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
||||
),
|
||||
"SRTLoader": build_template(suffixes=[".srt"], fileTypes=["srt"]),
|
||||
"TelegramChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
||||
"TextLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
||||
"UnstructuredWordDocumentLoader": build_template(
|
||||
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
||||
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||
"UnstructuredWordDocumentLoader": build_file_field(
|
||||
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
||||
),
|
||||
}
|
||||
|
|
@ -52,7 +56,54 @@ class DocumentLoaderFrontNode(FrontendNode):
|
|||
def add_extra_fields(self) -> None:
|
||||
name = None
|
||||
display_name = "Web Page"
|
||||
if self.template.type_name in self.file_path_templates:
|
||||
if self.template.type_name in {"GitLoader"}:
|
||||
# Add fields repo_path, clone_url, branch and file_filter
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=True,
|
||||
show=True,
|
||||
name="repo_path",
|
||||
value="",
|
||||
display_name="Path to repository",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=False,
|
||||
show=True,
|
||||
name="clone_url",
|
||||
value="",
|
||||
display_name="Clone URL",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=True,
|
||||
show=True,
|
||||
name="branch",
|
||||
value="",
|
||||
display_name="Branch",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=False,
|
||||
show=True,
|
||||
name="file_filter",
|
||||
value="",
|
||||
display_name="File extensions (comma-separated)",
|
||||
advanced=False,
|
||||
)
|
||||
)
|
||||
|
||||
elif self.template.type_name in self.file_path_templates:
|
||||
self.template.add_field(self.file_path_templates[self.template.type_name])
|
||||
elif self.template.type_name in {
|
||||
"WebBaseLoader",
|
||||
|
|
@ -65,7 +116,11 @@ class DocumentLoaderFrontNode(FrontendNode):
|
|||
name = "web_path"
|
||||
elif self.template.type_name in {"GitbookLoader"}:
|
||||
name = "web_page"
|
||||
elif self.template.type_name in {"DirectoryLoader", "ReadTheDocsLoader"}:
|
||||
elif self.template.type_name in {
|
||||
"DirectoryLoader",
|
||||
"ReadTheDocsLoader",
|
||||
"NotionDirectoryLoader",
|
||||
}:
|
||||
name = "path"
|
||||
display_name = "Local directory"
|
||||
if name:
|
||||
|
|
@ -90,3 +145,23 @@ class DocumentLoaderFrontNode(FrontendNode):
|
|||
display_name="glob",
|
||||
)
|
||||
)
|
||||
# add a metadata field of type dict
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="code",
|
||||
required=True,
|
||||
show=True,
|
||||
name="metadata",
|
||||
value="{}",
|
||||
display_name="Metadata",
|
||||
multiline=False,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
FrontendNode.format_field(field, name)
|
||||
if field.name == "metadata":
|
||||
field.show = True
|
||||
field.advanced = False
|
||||
field.show = True
|
||||
|
|
|
|||
|
|
@ -18,6 +18,27 @@ class MemoryFrontendNode(FrontendNode):
|
|||
value=False,
|
||||
)
|
||||
)
|
||||
# add input_key and output_key str fields
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=False,
|
||||
show=True,
|
||||
name="input_key",
|
||||
advanced=True,
|
||||
value="",
|
||||
)
|
||||
)
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=False,
|
||||
show=True,
|
||||
name="output_key",
|
||||
advanced=True,
|
||||
value="",
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
|
|
@ -36,3 +57,10 @@ class MemoryFrontendNode(FrontendNode):
|
|||
field.required = False
|
||||
field.show = True
|
||||
field.advanced = False
|
||||
if field.name in ["input_key", "output_key"]:
|
||||
field.required = False
|
||||
field.show = True
|
||||
field.advanced = False
|
||||
field.value = ""
|
||||
if field.name == "memory_key":
|
||||
field.value = "chat_history"
|
||||
|
|
|
|||
|
|
@ -96,6 +96,16 @@ class PythonFunctionToolNode(FrontendNode):
|
|||
name="code",
|
||||
advanced=False,
|
||||
),
|
||||
TemplateField(
|
||||
field_type="bool",
|
||||
required=True,
|
||||
placeholder="",
|
||||
is_list=False,
|
||||
show=True,
|
||||
multiline=False,
|
||||
value=False,
|
||||
name="return_direct",
|
||||
),
|
||||
],
|
||||
)
|
||||
description: str = "Python function to be executed."
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from langflow.template.field.base import TemplateField
|
||||
from langflow.template.frontend_node.base import FrontendNode
|
||||
|
|
@ -6,6 +6,19 @@ from langflow.template.frontend_node.base import FrontendNode
|
|||
|
||||
class VectorStoreFrontendNode(FrontendNode):
|
||||
def add_extra_fields(self) -> None:
|
||||
extra_fields: List[TemplateField] = []
|
||||
# Add search_kwargs field
|
||||
extra_field = TemplateField(
|
||||
name="search_kwargs",
|
||||
field_type="code",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="{}",
|
||||
)
|
||||
extra_fields.append(extra_field)
|
||||
if self.template.type_name == "Weaviate":
|
||||
extra_field = TemplateField(
|
||||
name="weaviate_url",
|
||||
|
|
@ -17,8 +30,174 @@ class VectorStoreFrontendNode(FrontendNode):
|
|||
multiline=False,
|
||||
value="http://localhost:8080",
|
||||
)
|
||||
# Add client_kwargs field
|
||||
extra_field2 = TemplateField(
|
||||
name="client_kwargs",
|
||||
field_type="code",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="{}",
|
||||
)
|
||||
extra_fields.extend((extra_field, extra_field2))
|
||||
|
||||
self.template.add_field(extra_field)
|
||||
elif self.template.type_name == "Chroma":
|
||||
# New bool field for persist parameter
|
||||
extra_field = TemplateField(
|
||||
name="persist",
|
||||
field_type="bool",
|
||||
required=False,
|
||||
show=True,
|
||||
advanced=False,
|
||||
value=True,
|
||||
display_name="Persist",
|
||||
)
|
||||
extra_fields.append(extra_field)
|
||||
elif self.template.type_name == "Pinecone":
|
||||
# add pinecone_api_key and pinecone_env
|
||||
extra_field = TemplateField(
|
||||
name="pinecone_api_key",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
extra_field2 = TemplateField(
|
||||
name="pinecone_env",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
extra_fields.extend((extra_field, extra_field2))
|
||||
elif self.template.type_name == "FAISS":
|
||||
extra_field = TemplateField(
|
||||
name="folder_path",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
display_name="Local Path",
|
||||
value="",
|
||||
)
|
||||
extra_field2 = TemplateField(
|
||||
name="index_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
show=True,
|
||||
advanced=False,
|
||||
value="",
|
||||
display_name="Index Name",
|
||||
)
|
||||
extra_fields.extend((extra_field, extra_field2))
|
||||
elif self.template.type_name == "SupabaseVectorStore":
|
||||
self.display_name = "Supabase"
|
||||
# Add table_name and query_name
|
||||
extra_field = TemplateField(
|
||||
name="table_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
extra_field2 = TemplateField(
|
||||
name="query_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
# Add supabase_url and supabase_service_key
|
||||
extra_field3 = TemplateField(
|
||||
name="supabase_url",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
extra_field4 = TemplateField(
|
||||
name="supabase_service_key",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
)
|
||||
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||
|
||||
elif self.template.type_name == "MongoDBAtlasVectorSearch":
|
||||
self.display_name = "MongoDB Atlas"
|
||||
|
||||
extra_field = TemplateField(
|
||||
name="mongodb_atlas_cluster_uri",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
display_name="MongoDB Atlas Cluster URI",
|
||||
value="",
|
||||
)
|
||||
extra_field2 = TemplateField(
|
||||
name="collection_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
display_name="Collection Name",
|
||||
value="",
|
||||
)
|
||||
extra_field3 = TemplateField(
|
||||
name="db_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
display_name="Database Name",
|
||||
value="",
|
||||
)
|
||||
extra_field4 = TemplateField(
|
||||
name="index_name",
|
||||
field_type="str",
|
||||
required=False,
|
||||
placeholder="",
|
||||
show=True,
|
||||
advanced=True,
|
||||
multiline=False,
|
||||
display_name="Index Name",
|
||||
value="",
|
||||
)
|
||||
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||
|
||||
if extra_fields:
|
||||
for field in extra_fields:
|
||||
self.template.add_field(field)
|
||||
|
||||
def add_extra_base_classes(self) -> None:
|
||||
self.base_classes.append("BaseRetriever")
|
||||
|
|
@ -27,7 +206,25 @@ class VectorStoreFrontendNode(FrontendNode):
|
|||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
FrontendNode.format_field(field, name)
|
||||
# Define common field attributes
|
||||
basic_fields = ["work_dir", "collection_name", "api_key", "location"]
|
||||
basic_fields = [
|
||||
"work_dir",
|
||||
"collection_name",
|
||||
"api_key",
|
||||
"location",
|
||||
"persist_directory",
|
||||
"persist",
|
||||
"weaviate_url",
|
||||
"index_name",
|
||||
"namespace",
|
||||
"folder_path",
|
||||
"table_name",
|
||||
"query_name",
|
||||
"supabase_url",
|
||||
"supabase_service_key",
|
||||
"mongodb_atlas_cluster_uri",
|
||||
"collection_name",
|
||||
"db_name",
|
||||
]
|
||||
advanced_fields = [
|
||||
"n_dim",
|
||||
"key",
|
||||
|
|
@ -43,14 +240,21 @@ class VectorStoreFrontendNode(FrontendNode):
|
|||
"https",
|
||||
"prefer_grpc",
|
||||
"grpc_port",
|
||||
"pinecone_api_key",
|
||||
"pinecone_env",
|
||||
"client_kwargs",
|
||||
"search_kwargs",
|
||||
]
|
||||
|
||||
# Check and set field attributes
|
||||
if field.name == "texts":
|
||||
# if field.name is "texts" it has to be replaced
|
||||
# when instantiating the vectorstores
|
||||
field.name = "documents"
|
||||
|
||||
field.field_type = "TextSplitter"
|
||||
field.display_name = "Text Splitter"
|
||||
field.required = True
|
||||
field.display_name = "Documents"
|
||||
field.required = False
|
||||
field.show = True
|
||||
field.advanced = False
|
||||
|
||||
|
|
@ -78,5 +282,6 @@ class VectorStoreFrontendNode(FrontendNode):
|
|||
field.advanced = True
|
||||
if "key" in field.name:
|
||||
field.password = False
|
||||
# TODO: Weaviate requires weaviate_url to be passed as it is not part of
|
||||
# the class or from_texts method. We need the add_extra_fields to fix this
|
||||
|
||||
elif field.name == "text_key":
|
||||
field.show = False
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import Callable, Optional, Union
|
|||
from pydantic import BaseModel
|
||||
|
||||
from langflow.template.field.base import TemplateField
|
||||
from langflow.utils.constants import DIRECT_TYPES
|
||||
|
||||
|
||||
class Template(BaseModel):
|
||||
|
|
@ -18,8 +19,15 @@ class Template(BaseModel):
|
|||
for field in self.fields:
|
||||
format_field_func(field, name)
|
||||
|
||||
def sort_fields(self):
|
||||
# first sort alphabetically
|
||||
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
||||
self.fields.sort(key=lambda x: x.name)
|
||||
self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False)
|
||||
|
||||
def to_dict(self, format_field_func=None):
|
||||
self.process_fields(self.type_name, format_field_func)
|
||||
self.sort_fields()
|
||||
result = {field.name: field.to_dict() for field in self.fields}
|
||||
result["_type"] = self.type_name # type: ignore
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -36,3 +36,4 @@ def python_function(text: str) -> str:
|
|||
\"\"\"This is a default python function that returns the input text\"\"\"
|
||||
return text
|
||||
"""
|
||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||
|
|
|
|||
|
|
@ -299,12 +299,15 @@ def format_dict(d, name: Optional[str] = None):
|
|||
if name == "OpenAI" and key == "model_name":
|
||||
value["options"] = constants.OPENAI_MODELS
|
||||
value["list"] = True
|
||||
value["value"] = constants.OPENAI_MODELS[0]
|
||||
elif name == "ChatOpenAI" and key == "model_name":
|
||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||
value["list"] = True
|
||||
value["value"] = constants.CHAT_OPENAI_MODELS[0]
|
||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||
value["options"] = constants.ANTHROPIC_MODELS
|
||||
value["list"] = True
|
||||
value["value"] = constants.ANTHROPIC_MODELS[0]
|
||||
return d
|
||||
|
||||
|
||||
|
|
|
|||
3603
src/frontend/package-lock.json
generated
3603
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,15 +8,16 @@
|
|||
"@headlessui/react": "^1.7.10",
|
||||
"@heroicons/react": "^2.0.15",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-menubar": "^1.0.3",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-menubar": "^1.0.3",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@tabler/icons-react": "^2.18.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"daisyui": "^3.1.1",
|
||||
"postcss": "^8.4.23",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.0.2",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||
import {
|
||||
classNames,
|
||||
getRandomKeyByssmm,
|
||||
groupByFamily,
|
||||
isValidConnection,
|
||||
nodeIconsLucide,
|
||||
} from "../../../../utils";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import InputComponent from "../../../../components/inputComponent";
|
||||
|
|
@ -17,7 +19,7 @@ import InputFileComponent from "../../../../components/inputFileComponent";
|
|||
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||
import IntComponent from "../../../../components/intComponent";
|
||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||
import { nodeNames, nodeIcons } from "../../../../utils";
|
||||
import { nodeNames } from "../../../../utils";
|
||||
import React from "react";
|
||||
import { nodeColors } from "../../../../utils";
|
||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||
|
|
@ -40,7 +42,7 @@ export default function ParameterComponent({
|
|||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [position, setPosition] = useState(0);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const { setTabsState, tabId } = useContext(TabsContext);
|
||||
const { setTabsState, tabId, save } = useContext(TabsContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||
|
|
@ -82,18 +84,18 @@ export default function ParameterComponent({
|
|||
|
||||
refHtml.current = groupedObj.map((item, i) => (
|
||||
<span
|
||||
key={i}
|
||||
key={getRandomKeyByssmm()}
|
||||
className={classNames(
|
||||
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="h-5 w-5"
|
||||
className="h-6 w-6"
|
||||
style={{
|
||||
color: nodeColors[item.family],
|
||||
}}
|
||||
>
|
||||
{React.createElement(nodeIcons[item.family])}
|
||||
{React.createElement(nodeIconsLucide[item.family])}
|
||||
</div>
|
||||
<span className="ps-2 text-gray-950">
|
||||
{nodeNames[item.family] ?? ""}{" "}
|
||||
|
|
@ -102,14 +104,14 @@ export default function ParameterComponent({
|
|||
-
|
||||
{item.type.split(", ").length > 2
|
||||
? item.type.split(", ").map((el, i) => (
|
||||
<>
|
||||
<span key={i}>
|
||||
{i == item.type.split(", ").length - 1
|
||||
<React.Fragment key={el + i}>
|
||||
<span>
|
||||
{i === item.type.split(", ").length - 1
|
||||
? el
|
||||
: (el += `, `)}
|
||||
</span>
|
||||
{i % 2 == 0 && i > 0 && <br></br>}
|
||||
</>
|
||||
{i % 2 === 0 && i > 0 && <br />}
|
||||
</React.Fragment>
|
||||
))
|
||||
: item.type}
|
||||
</span>
|
||||
|
|
@ -239,7 +241,8 @@ export default function ParameterComponent({
|
|||
fileTypes={data.node.template[name].fileTypes}
|
||||
suffixes={data.node.template[name].suffixes}
|
||||
onFileChange={(t: string) => {
|
||||
data.node.template[name].content = t;
|
||||
data.node.template[name].file_path = t;
|
||||
save();
|
||||
}}
|
||||
></InputFileComponent>
|
||||
) : left === true && type === "int" ? (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
import { classNames, nodeColors, nodeIcons, toTitleCase } from "../../utils";
|
||||
import {
|
||||
classNames,
|
||||
nodeColors,
|
||||
nodeIconsLucide,
|
||||
toTitleCase,
|
||||
} from "../../utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { useContext, useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
ForwardRefExoticComponent,
|
||||
ComponentType,
|
||||
SVGProps,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
|
@ -12,6 +26,7 @@ import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarCom
|
|||
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import { useSSE } from "../../contexts/SSEContext";
|
||||
import { ReactElement } from "react-markdown/lib/react-markdown";
|
||||
|
||||
export default function GenericNode({
|
||||
data,
|
||||
|
|
@ -25,8 +40,9 @@ export default function GenericNode({
|
|||
const { types, deleteNode } = useContext(typesContext);
|
||||
|
||||
const { closePopUp, openPopUp } = useContext(PopUpContext);
|
||||
|
||||
const Icon = nodeIcons[data.type] || nodeIcons[types[data.type]];
|
||||
// any to avoid type conflict
|
||||
const Icon: any =
|
||||
nodeIconsLucide[data.type] || nodeIconsLucide[types[data.type]];
|
||||
const [validationStatus, setValidationStatus] = useState(null);
|
||||
// State for outline color
|
||||
const { sseData, isBuilding } = useSSE();
|
||||
|
|
@ -88,8 +104,13 @@ export default function GenericNode({
|
|||
}}
|
||||
/>
|
||||
<div className="ml-2 truncate">
|
||||
<ShadTooltip delayDuration={1500} content={data.type}>
|
||||
<div className="ml-2 truncate text-gray-800">{data.type}</div>
|
||||
<ShadTooltip
|
||||
delayDuration={1500}
|
||||
content={data.node.display_name}
|
||||
>
|
||||
<div className="ml-2 truncate text-gray-800">
|
||||
{data.node.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
import {
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
InformationCircleIcon,
|
||||
CheckCircleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { useState } from "react";
|
||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||
import { X, CheckCircle2, Info, XCircle } from "lucide-react";
|
||||
|
||||
export default function SingleAlert({
|
||||
dropItem,
|
||||
|
|
@ -34,7 +29,7 @@ export default function SingleAlert({
|
|||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon
|
||||
<XCircle
|
||||
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -70,7 +65,7 @@ export default function SingleAlert({
|
|||
className="inline-flex rounded-md bg-red-50 dark:bg-transparent p-1.5 text-red-500 dark:text-red-50"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,7 +76,7 @@ export default function SingleAlert({
|
|||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
<Info
|
||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -116,7 +111,7 @@ export default function SingleAlert({
|
|||
className="inline-flex rounded-md bg-blue-50 dark:bg-transparent p-1.5 text-blue-500 dark:text-blue-50"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -127,7 +122,7 @@ export default function SingleAlert({
|
|||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
<CheckCircle2
|
||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -150,7 +145,7 @@ export default function SingleAlert({
|
|||
className="inline-flex rounded-md bg-green-50 dark:bg-transparent p-1.5 text-green-500 dark:text-green-50"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useContext, useEffect, useRef } from "react";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
import SingleAlert from "./components/singleAlertComponent";
|
||||
import { AlertDropdownType } from "../../types/alerts";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||
import { X, Trash2 } from "lucide-react";
|
||||
|
||||
export default function AlertDropdown({}: AlertDropdownType) {
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const componentRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -36,13 +36,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
|||
setTimeout(clearNotificationList, 100);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||
<Trash2 className="w-[1.1rem] h-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ErrorAlertType } from "../../types/alerts";
|
||||
import { XCircle } from "lucide-react";
|
||||
|
||||
export default function ErrorAlert({
|
||||
title,
|
||||
|
|
@ -43,7 +43,7 @@ export default function ErrorAlert({
|
|||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon
|
||||
<XCircle
|
||||
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { NoticeAlertType } from "../../types/alerts";
|
||||
import { Info } from "lucide-react";
|
||||
|
||||
export default function NoticeAlert({
|
||||
title,
|
||||
|
|
@ -40,7 +40,7 @@ export default function NoticeAlert({
|
|||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
<Info
|
||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SuccessAlertType } from "../../types/alerts";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
|
||||
export default function SuccessAlert({
|
||||
title,
|
||||
|
|
@ -38,7 +38,7 @@ export default function SuccessAlert({
|
|||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
<CheckCircle2
|
||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Disclosure } from "@headlessui/react";
|
||||
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { classNames } from "../../utils";
|
||||
|
|
|
|||
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { RadialProgressType } from "../../types/components";
|
||||
|
||||
export default function LoadingSpinner({}) {
|
||||
return <></>;
|
||||
}
|
||||
21
src/frontend/src/components/ProgressBarComponent/index.tsx
Normal file
21
src/frontend/src/components/ProgressBarComponent/index.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { ReactElement, useContext, useEffect, useRef, useState } from "react";
|
||||
import { ProgressBarType } from "../../types/components";
|
||||
import { Progress } from "../../components/ui/progress";
|
||||
import { progressContext } from "../../contexts/ProgressContext";
|
||||
import { setInterval } from "timers/promises";
|
||||
|
||||
export default function ProgressBarComponent({
|
||||
value,
|
||||
children,
|
||||
}: ProgressBarType) {
|
||||
const ref = useRef(0);
|
||||
const reff = useRef();
|
||||
const { progress } = useContext(progressContext);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = progress * 100;
|
||||
console.log(progress);
|
||||
}, [progress]);
|
||||
|
||||
return <Progress className="h-2.5" value={ref.current} />;
|
||||
}
|
||||
19
src/frontend/src/components/RadialProgress/index.tsx
Normal file
19
src/frontend/src/components/RadialProgress/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { RadialProgressType } from "../../types/components";
|
||||
|
||||
export default function RadialProgressComponent({
|
||||
value,
|
||||
color,
|
||||
}: RadialProgressType) {
|
||||
const style = {
|
||||
"--value": value * 100,
|
||||
"--size": "1.5rem",
|
||||
"--thickness": "2px",
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<div className={"radial-progress " + color} style={style}>
|
||||
<strong className="text-[8px]">{Math.trunc(value * 100)}%</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { Zap } from "lucide-react";
|
||||
import { validateNodes } from "../../../utils";
|
||||
|
|
@ -9,6 +9,8 @@ import { typesContext } from "../../../contexts/typesContext";
|
|||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { postBuildInit } from "../../../controllers/API";
|
||||
|
||||
import RadialProgressComponent from "../../RadialProgress";
|
||||
|
||||
export default function BuildTrigger({
|
||||
open,
|
||||
flow,
|
||||
|
|
@ -20,9 +22,12 @@ export default function BuildTrigger({
|
|||
setIsBuilt: any;
|
||||
isBuilt: boolean;
|
||||
}) {
|
||||
const { updateSSEData, isBuilding, setIsBuilding } = useSSE();
|
||||
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const [isIconTouched, setIsIconTouched] = useState(false);
|
||||
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
async function handleBuild(flow: FlowType) {
|
||||
try {
|
||||
|
|
@ -58,12 +63,10 @@ export default function BuildTrigger({
|
|||
setIsBuilding(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function streamNodeData(flow: FlowType) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const response = await postBuildInit(flow);
|
||||
const { flowId } = response.data;
|
||||
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
|
|
@ -83,19 +86,23 @@ export default function BuildTrigger({
|
|||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
// TODO: implement the progress
|
||||
setSuccessData({ title: parsedData.log });
|
||||
setSuccessData({ title: parsedData.progress });
|
||||
} else {
|
||||
// Otherwise, process the data
|
||||
const isValid = processStreamResult(parsedData);
|
||||
setProgress(parsedData.progress);
|
||||
validationResults.push(isValid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
setIsBuilding(false);
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
|
|
@ -129,6 +136,14 @@ export default function BuildTrigger({
|
|||
}
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsIconTouched(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsIconTouched(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={!open}
|
||||
|
|
@ -142,17 +157,23 @@ export default function BuildTrigger({
|
|||
>
|
||||
<div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}>
|
||||
<div
|
||||
className="flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032]
|
||||
bg-[#E2E7EE] dark:border-gray-600 cursor-pointer"
|
||||
className={`${eventClick} flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032] bg-[#E2E7EE] dark:border-gray-600 cursor-pointer`}
|
||||
onClick={() => {
|
||||
handleBuild(flow);
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<button>
|
||||
<div className="flex gap-3 items-center">
|
||||
{isBuilding ? (
|
||||
{isBuilding && progress < 1 ? (
|
||||
// Render your loading animation here when isBuilding is true
|
||||
<Loading strokeWidth={1.5} style={{ color: "white" }} />
|
||||
<RadialProgressComponent
|
||||
color={"text-orange-400"}
|
||||
value={progress}
|
||||
></RadialProgressComponent>
|
||||
) : isBuilding ? (
|
||||
<Loading strokeWidth={1.5} style={{ color: "#fb923c" }} />
|
||||
) : (
|
||||
<Zap className="sh-6 w-6 fill-orange-400 stroke-1 stroke-orange-400" />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
import {
|
||||
ChatBubbleLeftEllipsisIcon,
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
PlusSmallIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { nodeColors } from "../../../utils";
|
||||
import Convert from "ansi-to-html";
|
||||
const convert = new Convert({ newline: true });
|
||||
import { MessageCircle } from "lucide-react";
|
||||
|
||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
|
@ -24,7 +20,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
|||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute top-2 right-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
||||
<MessageCircle className="w-5 h-5 animate-bounce" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import CodeAreaModal from "../../modals/codeAreaModal";
|
||||
import TextAreaModal from "../../modals/textAreaModal";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
|
||||
export default function CodeAreaComponent({
|
||||
value,
|
||||
|
|
@ -69,7 +69,7 @@ export default function CodeAreaComponent({
|
|||
}}
|
||||
>
|
||||
{!editNode && (
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring dark:text-gray-300 ml-3" />
|
||||
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300 ml-3" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { DropDownComponentType } from "../../types/components";
|
||||
import { classNames } from "../../utils";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { ChevronsUpDown, Check } from "lucide-react";
|
||||
|
||||
export default function Dropdown({
|
||||
value,
|
||||
|
|
@ -15,6 +15,9 @@ export default function Dropdown({
|
|||
let [internalValue, setInternalValue] = useState(
|
||||
value === "" || !value ? "Choose an option" : value
|
||||
);
|
||||
useEffect(() => {
|
||||
setInternalValue(value === "" || !value ? "Choose an option" : value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -43,7 +46,7 @@ export default function Dropdown({
|
|||
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||
}
|
||||
>
|
||||
<ChevronUpDownIcon
|
||||
<ChevronsUpDown
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -97,7 +100,7 @@ export default function Dropdown({
|
|||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
<Check
|
||||
className={
|
||||
active
|
||||
? "h-5 w-5 dark:text-black text-black"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
</Link>
|
||||
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="gap-2 flex items-center max-w-[200px]"
|
||||
variant="primary"
|
||||
|
|
@ -69,6 +69,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
onClick={() => {
|
||||
handleAddFlow();
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New
|
||||
|
|
@ -77,6 +78,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
onClick={() => {
|
||||
openPopUp(<FlowSettingsModal />);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 mr-2 dark:text-gray-300" />
|
||||
Settings
|
||||
|
|
@ -85,6 +87,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
onClick={() => {
|
||||
undo();
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Undo className="w-4 h-4 mr-2 dark:text-gray-300" />
|
||||
Undo
|
||||
|
|
@ -93,6 +96,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
onClick={() => {
|
||||
redo();
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Redo className="w-4 h-4 mr-2 dark:text-gray-300" />
|
||||
Redo
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { Link, useLocation, useParams } from "react-router-dom";
|
|||
import { USER_PROJECTS_HEADER } from "../../constants";
|
||||
import { getRepoStars } from "../../controllers/API";
|
||||
import { Separator } from "../ui/separator";
|
||||
import { Bell } from "lucide-react";
|
||||
|
||||
export default function Header() {
|
||||
const { flows, addFlow, tabId } = useContext(TabsContext);
|
||||
|
|
@ -98,7 +99,7 @@ export default function Header() {
|
|||
>
|
||||
<FaDiscord className="h-5 w-5" />
|
||||
</a>
|
||||
<Separator orientation="vertical" />
|
||||
{/* <Separator orientation="vertical" />
|
||||
<button
|
||||
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
|
||||
onClick={() => {
|
||||
|
|
@ -110,7 +111,7 @@ export default function Header() {
|
|||
) : (
|
||||
<MoonIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
</button> */}
|
||||
<button
|
||||
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200 relative"
|
||||
onClick={(event: React.MouseEvent<HTMLElement>) => {
|
||||
|
|
@ -134,7 +135,7 @@ export default function Header() {
|
|||
{notificationCenter && (
|
||||
<div className="absolute w-1.5 h-1.5 rounded-full bg-destructive right-[3px]"></div>
|
||||
)}
|
||||
<BellIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<Bell className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { FileComponentType } from "../../types/components";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { FileSearch2 } from "lucide-react";
|
||||
import { uploadFile } from "../../controllers/API";
|
||||
|
||||
export default function InputFileComponent({
|
||||
value,
|
||||
|
|
@ -14,7 +16,9 @@ export default function InputFileComponent({
|
|||
editNode = false,
|
||||
}: FileComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { tabId } = useContext(TabsContext);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
|
|
@ -23,12 +27,6 @@ export default function InputFileComponent({
|
|||
}
|
||||
}, [disabled, onChange]);
|
||||
|
||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
||||
fileReadEvent.preventDefault();
|
||||
const file = fileReadEvent.target.result;
|
||||
onFileChange(file as string);
|
||||
}
|
||||
|
||||
function checkFileType(fileName: string): boolean {
|
||||
for (let index = 0; index < suffixes.length; index++) {
|
||||
if (fileName.endsWith(suffixes[index])) {
|
||||
|
|
@ -43,27 +41,54 @@ export default function InputFileComponent({
|
|||
}, [value]);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = suffixes.join(",");
|
||||
input.style.display = "none";
|
||||
input.multiple = false;
|
||||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
|
||||
input.onchange = (e: Event) => {
|
||||
setLoading(true);
|
||||
|
||||
// Get the selected file
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
const fileData = new FileReader();
|
||||
fileData.onload = attachFile;
|
||||
|
||||
// Check if the file type is correct
|
||||
if (file && checkFileType(file.name)) {
|
||||
fileData.readAsDataURL(file);
|
||||
setMyValue(file.name);
|
||||
onChange(file.name);
|
||||
// Upload the file
|
||||
uploadFile(file, tabId)
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
console.log("File uploaded successfully");
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
console.log("File name:", file_path);
|
||||
|
||||
// Update the state and callback with the name of the file
|
||||
// sets the value to the user
|
||||
setMyValue(file.name);
|
||||
onChange(file.name);
|
||||
// sets the value that goes to the backend
|
||||
onFileChange(file_path);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Error occurred while uploading file");
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
// Show an error if the file type is not allowed
|
||||
setErrorData({
|
||||
title:
|
||||
"Please select a valid file. Only files this files are allowed:",
|
||||
"Please select a valid file. Only these file types are allowed:",
|
||||
list: fileTypes,
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
};
|
||||
|
||||
|
|
@ -73,7 +98,7 @@ export default function InputFileComponent({
|
|||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||
}
|
||||
>
|
||||
<div className="w-full flex items-center gap-3">
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<span
|
||||
onClick={handleButtonClick}
|
||||
className={
|
||||
|
|
@ -88,8 +113,11 @@ export default function InputFileComponent({
|
|||
{myValue !== "" ? myValue : "No file"}
|
||||
</span>
|
||||
<button onClick={handleButtonClick}>
|
||||
{!editNode && (
|
||||
<DocumentMagnifyingGlassIcon className="w-8 h-8 hover:text-ring" />
|
||||
{!editNode && !loading && (
|
||||
<FileSearch2 className="w-6 h-6 hover:text-ring" />
|
||||
)}
|
||||
{!editNode && loading && (
|
||||
<span className="loading loading-spinner loading-sm pl-3 h-8 pointer-events-none"></span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { InputListComponentType } from "../../types/components";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
|
||||
import _ from "lodash";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { X, Plus } from "lucide-react";
|
||||
|
||||
export default function InputListComponent({
|
||||
value,
|
||||
onChange,
|
||||
|
|
@ -59,7 +60,7 @@ export default function InputListComponent({
|
|||
onChange(inputList);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className={"w-4 h-4 hover:text-ring"} />
|
||||
<Plus className={"w-4 h-4 hover:text-ring"} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
|
|
@ -72,7 +73,7 @@ export default function InputListComponent({
|
|||
onChange(inputList);
|
||||
}}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||
<X className="w-4 h-4 hover:text-red-600" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
import GenericModal from "../../modals/genericModal";
|
||||
import { TypeModal } from "../../utils";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
|
||||
export default function PromptAreaComponent({
|
||||
value,
|
||||
|
|
@ -74,7 +74,7 @@ export default function PromptAreaComponent({
|
|||
}}
|
||||
>
|
||||
{!editNode && (
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
import GenericModal from "../../modals/genericModal";
|
||||
import { TypeModal } from "../../utils";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
|
||||
export default function TextAreaComponent({
|
||||
value,
|
||||
onChange,
|
||||
|
|
@ -76,7 +77,7 @@ export default function TextAreaComponent({
|
|||
}}
|
||||
>
|
||||
{!editNode && (
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const CardDescription = React.forwardRef<
|
|||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
|
|
|
|||
27
src/frontend/src/components/ui/progress.tsx
Normal file
27
src/frontend/src/components/ui/progress.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||
import { cn } from "../../utils";
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-full w-full flex-1 bg-primary transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
));
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||
|
||||
export { Progress };
|
||||
|
|
@ -66,7 +66,7 @@ export const getPythonApiCode = (flow: FlowType): string => {
|
|||
|
||||
BASE_API_URL = "${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/predict"
|
||||
}/api/v1/process"
|
||||
FLOW_ID = "${flowId}"
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
|
|
@ -83,7 +83,7 @@ def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
|
|||
"""
|
||||
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||
|
||||
payload = {"message": message}
|
||||
payload = {"inputs": {"input": message}}
|
||||
|
||||
if tweaks:
|
||||
payload["tweaks"] = tweaks
|
||||
|
|
@ -106,9 +106,9 @@ export const getCurlCode = (flow: FlowType): string => {
|
|||
return `curl -X POST \\
|
||||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/predict/${flowId} \\
|
||||
}/api/v1/process/${flowId} \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-d '{"message": "Your message", "tweaks": ${JSON.stringify(
|
||||
-d '{"inputs": {"input": message}, "tweaks": ${JSON.stringify(
|
||||
tweaks,
|
||||
null,
|
||||
2
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "../utils";
|
||||
import { alertContext } from "./alertContext";
|
||||
import { typesContext } from "./typesContext";
|
||||
import { APITemplateType } from "../types/api";
|
||||
import { APIClassType, APITemplateType } from "../types/api";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { addEdge } from "reactflow";
|
||||
import {
|
||||
|
|
@ -192,20 +192,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
function processFlowEdges(flow) {
|
||||
if(!flow.data || !flow.data.edges) return;
|
||||
flow.data.edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
edge.style = { stroke: "#555555" };
|
||||
});
|
||||
}
|
||||
function updateDisplay_name(node:NodeType,template:APIClassType) {
|
||||
node.data.node.display_name = template["display_name"]?template["display_name"]:node.data.type;
|
||||
}
|
||||
|
||||
function processFlowNodes(flow) {
|
||||
flow.data.nodes.forEach((node) => {
|
||||
if(!flow.data || !flow.data.nodes) return;
|
||||
flow.data.nodes.forEach((node:NodeType) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
updateDisplay_name(node,template);
|
||||
updateNodeBaseClasses(node, template);
|
||||
updateNodeEdges(flow, node, template);
|
||||
updateNodeDescription(node, template);
|
||||
|
|
@ -214,11 +220,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
|
||||
function updateNodeBaseClasses(node, template) {
|
||||
function updateNodeBaseClasses(node:NodeType,template:APIClassType) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
}
|
||||
|
||||
function updateNodeEdges(flow, node, template) {
|
||||
function updateNodeEdges(flow:FlowType, node:NodeType,template:APIClassType) {
|
||||
flow.data.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
|
|
@ -230,11 +236,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
|
||||
function updateNodeDescription(node, template) {
|
||||
function updateNodeDescription(node:NodeType,template:APIClassType) {
|
||||
node.data.node.description = template["description"];
|
||||
}
|
||||
|
||||
function updateNodeTemplate(node, template) {
|
||||
function updateNodeTemplate(node:NodeType,template:APIClassType) {
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
|
|
@ -463,6 +469,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const newFlow = createNewFlow(flowData, flow);
|
||||
processFlowEdges(newFlow);
|
||||
processFlowNodes(newFlow);
|
||||
|
||||
try {
|
||||
const { id } = await saveFlowToDatabase(newFlow);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
PromptTypeAPI,
|
||||
errorsTypeAPI,
|
||||
InitTypeAPI,
|
||||
UploadFileTypeAPI,
|
||||
} from "./../../types/api/index";
|
||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
|
|
@ -48,13 +49,6 @@ export async function postValidateCode(
|
|||
return await axios.post("/api/v1/validate/code", { code });
|
||||
}
|
||||
|
||||
export async function postValidateNode(
|
||||
nodeId: string,
|
||||
data: any
|
||||
): Promise<AxiosResponse<string>> {
|
||||
return await axios.post(`/api/v1/validate/node/${nodeId}`, { data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the prompt for the code block by sending it to an API endpoint.
|
||||
*
|
||||
|
|
@ -319,3 +313,21 @@ export async function postBuildInit(
|
|||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await axios.post(`/api/v1/build/init`, flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
// method: "POST",
|
||||
// body: formData,
|
||||
// });
|
||||
/**
|
||||
* Uploads a file to the server.
|
||||
* @param {File} file - The file to upload.
|
||||
* @param {string} id - The ID of the flow to upload the file to.
|
||||
*/
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
id: string
|
||||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return await axios.post(`/api/v1/upload/${id}`, formData);
|
||||
}
|
||||
|
|
|
|||
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { ReactComponent as MongoDBSVG } from "./mongodb-icon.svg";
|
||||
|
||||
export const MongoDBIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <MongoDBSVG ref={ref} {...props} />;
|
||||
});
|
||||
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 32 32"><path d="M15.9.087l.854 1.604c.192.296.4.558.645.802.715.715 1.394 1.464 2.004 2.266 1.447 1.9 2.423 4.01 3.12 6.292.418 1.394.645 2.824.662 4.27.07 4.323-1.412 8.035-4.4 11.12-.488.488-1.01.94-1.57 1.342-.296 0-.436-.227-.558-.436-.227-.383-.366-.82-.436-1.255-.105-.523-.174-1.046-.14-1.586v-.244C16.057 24.21 15.796.21 15.9.087z" fill="#599636"/><path d="M15.9.034c-.035-.07-.07-.017-.105.017.017.35-.105.662-.296.96-.21.296-.488.523-.767.767-1.55 1.342-2.77 2.963-3.747 4.776-1.3 2.44-1.97 5.055-2.16 7.808-.087.993.314 4.497.627 5.508.854 2.684 2.388 4.933 4.375 6.885.488.47 1.01.906 1.55 1.325.157 0 .174-.14.21-.244a4.78 4.78 0 0 0 .157-.68l.35-2.614L15.9.034z" fill="#6cac48"/><path d="M16.754 28.845c.035-.4.227-.732.436-1.063-.21-.087-.366-.26-.488-.453-.105-.174-.192-.383-.26-.575-.244-.732-.296-1.5-.366-2.248v-.453c-.087.07-.105.662-.105.75a17.37 17.37 0 0 1-.314 2.353c-.052.314-.087.627-.28.906 0 .035 0 .07.017.122.314.924.4 1.865.453 2.824v.35c0 .418-.017.33.33.47.14.052.296.07.436.174.105 0 .122-.087.122-.157l-.052-.575v-1.604c-.017-.28.035-.558.07-.82z" fill="#c2bfbf"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { ReactComponent as PineconeSVG } from "./pinecone_logo.svg";
|
||||
|
||||
export const PineconeIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <PineconeSVG ref={ref} {...props} />;
|
||||
});
|
||||
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<svg width="32" height="35" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.8555 34.2962C14.9325 34.2962 15.8055 33.4451 15.8055 32.3954C15.8055 31.3456 14.9325 30.4946 13.8555 30.4946C12.7786 30.4946 11.9055 31.3456 11.9055 32.3954C11.9055 33.4451 12.7786 34.2962 13.8555 34.2962Z" fill="black"/>
|
||||
<path d="M18.4138 7.19675L19.2512 2.66005" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||
<path d="M22.2656 5.5855L19.3466 2.11099L15.3748 4.37292" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M14.9202 26.5528L15.7337 22.0169" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||
<path d="M18.7729 24.9304L15.83 21.4671L11.8701 23.741" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M16.6077 17.1996L17.4212 12.6633" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||
<path d="M20.4587 15.58L17.5277 12.128L13.5679 14.3904" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M8.32871 26.1554L4.75171 28.5815" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||
<path d="M8.54383 30.0865L4.3208 28.8738L4.63185 24.5944" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M21.3213 28.4299L23.8096 31.9282" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||
<path d="M19.718 32.045L24.1085 32.3365L25.3527 28.2438" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M25.3999 21.3291L29.7784 22.0996" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||
<path d="M26.9072 25.072L30.3048 22.1919L28.1634 18.3557" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M24.1196 12.8615L28.0197 10.763" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||
<path d="M24.3357 8.83965L28.4869 10.5188L27.7093 14.8216" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M6.91639 18.1572L2.52588 17.4101" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||
<path d="M4.17731 21.1645L2 17.328L5.36167 14.436" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M11.0799 10.6129L8.14893 7.34769" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||
<path d="M12.2897 6.77496L7.80349 6.96156L7.01392 11.2649" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
9
src/frontend/src/icons/supabase/index.tsx
Normal file
9
src/frontend/src/icons/supabase/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { ReactComponent as SupabaseSvg } from "./supabase-icon.svg";
|
||||
|
||||
export const SupabaseIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <SupabaseSvg ref={ref} {...props} />;
|
||||
});
|
||||
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
id="svg20"
|
||||
sodipodi:docname="supabase-icon.svg"
|
||||
style="fill:none"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata24">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1687"
|
||||
inkscape:window-height="849"
|
||||
id="namedview22"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0884956"
|
||||
inkscape:cx="54.5"
|
||||
inkscape:cy="56.5"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg20" />
|
||||
<path
|
||||
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||
id="path2"
|
||||
style="fill:url(#paint0_linear);stroke-width:0.57177335"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||
id="path4"
|
||||
style="fill:url(#paint1_linear);fill-opacity:0.2;stroke-width:0.57177335"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 26.89694,1.0634102 c 1.634986,-2.05918508 4.950125,-0.93090008 4.989521,1.698149 L 32.138899,41.214003 H 6.607076 c -4.6832501,0 -7.29518376,-5.409032 -4.3830007,-9.07673 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#3ecf8e;stroke-width:0.57177335" />
|
||||
<defs
|
||||
id="defs18">
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
x1="53.973801"
|
||||
y1="54.973999"
|
||||
x2="94.163498"
|
||||
y2="71.829498"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||
<stop
|
||||
stop-color="#249361"
|
||||
id="stop8" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#3ECF8E"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear"
|
||||
x1="36.1558"
|
||||
y1="30.577999"
|
||||
x2="54.484402"
|
||||
y2="65.080597"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||
<stop
|
||||
id="stop13" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-opacity="0"
|
||||
id="stop15" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -1,4 +1,3 @@
|
|||
import { CodeBracketSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import "ace-builds/src-noconflict/mode-python";
|
||||
|
|
@ -26,7 +25,7 @@ import {
|
|||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
import { Check, Clipboard } from "lucide-react";
|
||||
import { Check, Clipboard, Code2 } from "lucide-react";
|
||||
|
||||
export default function ApiModal({ flow }: { flow: FlowType }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
|
@ -88,7 +87,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
|
|||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">
|
||||
<span className="pr-2">Code</span>
|
||||
<CodeBracketSquareIcon
|
||||
<Code2
|
||||
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -104,7 +103,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
|
|||
<div className="flex items-center justify-between px-2">
|
||||
<TabsList>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTrigger value={index.toString()}>{tab.name}</TabsTrigger>
|
||||
<TabsTrigger key={index} value={index.toString()}>{tab.name}</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<div className="float-right">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
||||
import { VariableIcon } from "@heroicons/react/24/outline";
|
||||
import InputListComponent from "../../components/inputListComponent";
|
||||
import TextAreaComponent from "../../components/textAreaComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
|
|
@ -33,6 +32,7 @@ import {
|
|||
} from "../../components/ui/dialog";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Variable } from "lucide-react";
|
||||
|
||||
export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
|
@ -79,8 +79,8 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={setModalOpen}>
|
||||
<DialogTrigger></DialogTrigger>
|
||||
<Dialog open={true} onOpenChange={setModalOpen} >
|
||||
<DialogTrigger asChild></DialogTrigger>
|
||||
<DialogContent className="lg:max-w-[700px] ">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">
|
||||
|
|
@ -90,9 +90,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
|||
<DialogDescription>
|
||||
{data.node?.description}
|
||||
<div className="flex pt-4">
|
||||
<VariableIcon className="w-5 h-5 pe-1 text-gray-700 stroke-2 dark:text-slate-200">
|
||||
|
||||
</VariableIcon>
|
||||
<Variable className="w-5 h-5 pe-1 text-gray-700 stroke-2 dark:text-slate-200"></Variable>
|
||||
<span className="text-sm font-semibold text-gray-800 dark:text-white">
|
||||
Parameters
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export default function ModalField({
|
|||
data.node.template[name].value = t;
|
||||
setEnabled(t);
|
||||
}}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : type === "float" ? (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
|
|
@ -13,6 +12,7 @@ import {
|
|||
} from "../../utils";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import ModalField from "./components/ModalField";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
|
@ -27,7 +27,8 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
|||
}, 300);
|
||||
}
|
||||
}
|
||||
const Icon = nodeIcons[types[data.type]];
|
||||
// any to avoid type conflict
|
||||
const Icon: any = nodeIcons[types[data.type]];
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
|
|
@ -69,7 +70,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
|||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
<X className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
||||
import { classNames } from "../../../utils";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { INPUT_STYLE } from "../../../constants";
|
||||
import { Lock, Send } from "lucide-react";
|
||||
|
||||
export default function ChatInput({
|
||||
lockChat,
|
||||
chatValue,
|
||||
|
|
@ -60,12 +61,12 @@ export default function ChatInput({
|
|||
<div className="absolute bottom-0.5 right-3">
|
||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||
{lockChat ? (
|
||||
<LockClosedIcon
|
||||
<Lock
|
||||
className="h-5 w-5 text-gray-500 dark:hover:text-gray-300 animate-pulse"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<PaperAirplaneIcon
|
||||
<Send
|
||||
className="h-5 w-5 text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { classNames } from "../../../utils";
|
||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||
import AiIconStill from "../../../assets/froze-flow.png";
|
||||
import { UserIcon } from "@heroicons/react/24/solid";
|
||||
import FileCard from "../fileComponent";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeMathjax from "rehype-mathjax";
|
||||
|
|
@ -12,6 +10,7 @@ import remarkGfm from "remark-gfm";
|
|||
import remarkMath from "remark-math";
|
||||
import { CodeBlock } from "./codeBlock";
|
||||
import Convert from "ansi-to-html";
|
||||
import { User2, MessageCircle } from "lucide-react";
|
||||
|
||||
export default function ChatMessage({
|
||||
chat,
|
||||
|
|
@ -62,7 +61,7 @@ export default function ChatMessage({
|
|||
</div>
|
||||
)}
|
||||
{chat.isSend && (
|
||||
<UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />
|
||||
<User2 className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />
|
||||
)}
|
||||
</div>
|
||||
{!chat.isSend ? (
|
||||
|
|
@ -73,7 +72,7 @@ export default function ChatMessage({
|
|||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute -top-1 -left-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
||||
<MessageCircle className="w-5 h-5 animate-bounce dark:text-white" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
|
|
@ -152,7 +151,7 @@ export default function ChatMessage({
|
|||
</div>
|
||||
) : (
|
||||
<div className="w-full flex items-center">
|
||||
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
||||
<div className="text-start inline-block px-3 text-gray-600 dark:text-white">
|
||||
<span
|
||||
className="text-gray-600 dark:text-gray-200"
|
||||
dangerouslySetInnerHTML={{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||
import * as base64js from "base64-js";
|
||||
import { useState } from "react";
|
||||
import { DownloadCloud, File } from "lucide-react";
|
||||
|
||||
export default function FileCard({ fileName, content, fileType }) {
|
||||
const handleDownload = () => {
|
||||
|
|
@ -43,7 +43,7 @@ export default function FileCard({ fileName, content, fileType }) {
|
|||
className="text-gray-500 py-1 px-2 dark:bg-gray-700 dark:text-gray-300"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
|
||||
<DownloadCloud className="hover:scale-110 w-5 h-5 text-current" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -65,14 +65,14 @@ export default function FileCard({ fileName, content, fileType }) {
|
|||
className="w-8 h-8"
|
||||
/>
|
||||
) : (
|
||||
<DocumentIcon className="w-8 h-8" />
|
||||
<File className="w-8 h-8" />
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
{" "}
|
||||
<div className="truncate text-sm text-current">{fileName}</div>
|
||||
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
||||
</div>
|
||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||
<DownloadCloud className="w-6 h-6 text-current ml-auto" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { validateNodes } from "../../utils";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import ChatMessage from "./chatMessage";
|
||||
import { Eraser } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
import { X, MessagesSquare, Eraser } from "lucide-react";
|
||||
import { sendAllProps } from "../../types/api";
|
||||
import { ChatMessageType } from "../../types/chat";
|
||||
import ChatInput from "./chatInput";
|
||||
|
|
@ -387,7 +385,7 @@ export default function ChatModal({
|
|||
<span className="text-base text-gray-500">
|
||||
Start a conversation and click the agent’s thoughts{" "}
|
||||
<span>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
|
||||
<MessagesSquare className="w-5 h-5 inline animate-bounce mx-1 " />
|
||||
</span>{" "}
|
||||
to inspect the chaining process.
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { XMarkIcon, CommandLineIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import AceEditor from "react-ace";
|
||||
|
|
@ -22,6 +21,7 @@ import {
|
|||
} from "../../components/ui/dialog";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
|
||||
import { TerminalSquare } from "lucide-react";
|
||||
|
||||
export default function CodeAreaModal({
|
||||
value,
|
||||
|
|
@ -51,7 +51,7 @@ export default function CodeAreaModal({
|
|||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">
|
||||
<span className="pr-2">Edit Code</span>
|
||||
<CommandLineIcon
|
||||
<TerminalSquare
|
||||
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { XMarkIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
import { Button } from "../../components/ui/button";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants";
|
||||
import { FileText } from "lucide-react";
|
||||
|
||||
export default function GenericModal({
|
||||
value,
|
||||
|
|
@ -54,7 +54,7 @@ export default function GenericModal({
|
|||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">
|
||||
<span className="pr-2">{myModalTitle}</span>
|
||||
<DocumentTextIcon
|
||||
<FileText
|
||||
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/solid";
|
||||
import React, { ReactNode } from "react";
|
||||
import { classNames } from "../../../utils";
|
||||
import Tooltip from "../../../components/TooltipComponent";
|
||||
|
||||
export default function ButtonBox({
|
||||
onClick,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { DisclosureComponentType } from "../../../../types/components";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
|
||||
export default function DisclosureComponent({
|
||||
button: { title, Icon, buttons = [] },
|
||||
|
|
@ -14,7 +14,7 @@ export default function DisclosureComponent({
|
|||
<div>
|
||||
<Disclosure.Button className="select-none bg-muted dark:bg-gray-700/60 dark:border-y-gray-600 w-full flex justify-between items-center -mt-px px-3 py-2 border-y border-y-gray-200">
|
||||
<div className="flex gap-4">
|
||||
<Icon className="w-6 text-gray-800 dark:text-white/80" />
|
||||
<Icon size={22} className="text-gray-800 dark:text-white/80" />
|
||||
<span className="flex items-center text-sm text-gray-800 dark:text-white/80 font-medium">
|
||||
{title}
|
||||
</span>
|
||||
|
|
@ -26,7 +26,7 @@ export default function DisclosureComponent({
|
|||
</button>
|
||||
))}
|
||||
<div>
|
||||
<ChevronRightIcon
|
||||
<ChevronRight
|
||||
className={`${
|
||||
open || openDisc ? "rotate-90 transform" : ""
|
||||
} h-4 w-4 text-gray-800 dark:text-white`}
|
||||
|
|
|
|||
|
|
@ -398,6 +398,8 @@ export default function Page({ flow }: { flow: FlowType }) {
|
|||
zoomOnDoubleClick={!disableCopyPaste}
|
||||
selectNodesOnDrag={false}
|
||||
className="theme-attribution"
|
||||
minZoom={0.01}
|
||||
maxZoom={8}
|
||||
>
|
||||
<Background className="dark:bg-gray-900" />
|
||||
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
import { Bars2Icon } from "@heroicons/react/24/outline";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import {
|
||||
classNames,
|
||||
nodeColors,
|
||||
nodeIcons,
|
||||
nodeIconsLucide,
|
||||
nodeNames,
|
||||
} from "../../../../utils";
|
||||
import { useContext, useState } from "react";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||
import { Code2, FileDown, FileUp, Save } from "lucide-react";
|
||||
import { Code2, FileDown, FileUp, Save, Search } from "lucide-react";
|
||||
import { PopUpContext } from "../../../../contexts/popUpContext";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
import ApiModal from "../../../../modals/ApiModal";
|
||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
import { updateFlowInDatabase } from "../../../../controllers/API";
|
||||
import { INPUT_STYLE } from "../../../../constants";
|
||||
import { Separator } from "../../../../components/ui/separator";
|
||||
import { Menu } from "lucide-react";
|
||||
|
||||
export default function ExtraSidebar() {
|
||||
const { data } = useContext(typesContext);
|
||||
|
|
@ -135,7 +133,7 @@ export default function ExtraSidebar() {
|
|||
}}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
|
||||
<MagnifyingGlassIcon className="h-5 w-5 dark:text-white"></MagnifyingGlassIcon>
|
||||
<Search size={20} color="#000000" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -146,10 +144,10 @@ export default function ExtraSidebar() {
|
|||
Object.keys(dataFilter[d]).length > 0 ? (
|
||||
<DisclosureComponent
|
||||
openDisc={search.length == 0 ? false : true}
|
||||
key={i}
|
||||
key={nodeNames[d]}
|
||||
button={{
|
||||
title: nodeNames[d] ?? nodeNames.unknown,
|
||||
Icon: nodeIcons[d] ?? nodeIcons.unknown,
|
||||
Icon: nodeIconsLucide[d] ?? nodeIconsLucide.unknown,
|
||||
}}
|
||||
>
|
||||
<div className="p-2 flex flex-col gap-2">
|
||||
|
|
@ -157,9 +155,10 @@ export default function ExtraSidebar() {
|
|||
.sort()
|
||||
.map((t: string, k) => (
|
||||
<ShadTooltip
|
||||
content={t}
|
||||
content={data[d][t].display_name}
|
||||
delayDuration={1500}
|
||||
side="right"
|
||||
key={data[d][t].display_name}
|
||||
>
|
||||
<div key={k} data-tooltip-id={t}>
|
||||
<div
|
||||
|
|
@ -185,9 +184,9 @@ export default function ExtraSidebar() {
|
|||
>
|
||||
<div className="flex w-full justify-between text-sm px-3 py-1 bg-white dark:bg-gray-800 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||
<span className="text-black dark:text-white w-full pr-1 truncate text-xs">
|
||||
{t}
|
||||
{data[d][t].display_name}
|
||||
</span>
|
||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||
<Menu className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export type APIClassType = {
|
|||
base_classes: Array<string>;
|
||||
description: string;
|
||||
template: APITemplateType;
|
||||
display_name: string;
|
||||
[key: string]: Array<string> | string | APITemplateType;
|
||||
};
|
||||
export type TemplateVariableType = {
|
||||
|
|
@ -46,3 +47,8 @@ export type BuildStatusTypeAPI = {
|
|||
export type InitTypeAPI = {
|
||||
flowId: string;
|
||||
};
|
||||
|
||||
export type UploadFileTypeAPI = {
|
||||
file_path: string;
|
||||
flowId: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react";
|
||||
import {
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
SVGProps,
|
||||
} from "react";
|
||||
import { NodeDataType } from "../flow/index";
|
||||
import { typesContextType } from "../typesContext";
|
||||
export type InputComponentType = {
|
||||
|
|
@ -65,7 +71,7 @@ export type DisclosureComponentType = {
|
|||
openDisc: boolean;
|
||||
button: {
|
||||
title: string;
|
||||
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
Icon: any;
|
||||
buttons?: {
|
||||
Icon: ReactElement;
|
||||
title: string;
|
||||
|
|
@ -98,3 +104,14 @@ export type TooltipComponentType = {
|
|||
| "top-start"
|
||||
| "top";
|
||||
};
|
||||
|
||||
export type ProgressBarType = {
|
||||
children?: ReactElement;
|
||||
value?: number;
|
||||
max?: number;
|
||||
};
|
||||
|
||||
export type RadialProgressType = {
|
||||
value?: number;
|
||||
color?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { HomeIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export type sidebarNavigationItemType = {
|
||||
name: string;
|
||||
href: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { ReactFlowInstance } from "reactflow";
|
||||
import { APIClassType } from "../api";
|
||||
|
||||
const types: { [char: string]: string } = {};
|
||||
const template: { [char: string]: string } = {};
|
||||
const template: { [char: string]: APIClassType } = {};
|
||||
const data: { [char: string]: string } = {};
|
||||
|
||||
export type typesContextType = {
|
||||
|
|
|
|||
|
|
@ -39,9 +39,33 @@ import { OpenAiIcon } from "./icons/OpenAi";
|
|||
import { QDrantIcon } from "./icons/QDrant";
|
||||
import { SearxIcon } from "./icons/Searx";
|
||||
import { SlackIcon } from "./icons/Slack";
|
||||
import { PineconeIcon } from "./icons/Pinecone";
|
||||
import clsx, { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
|
||||
import { ComponentType, SVGProps } from "react";
|
||||
import {
|
||||
Boxes,
|
||||
Cpu,
|
||||
Fingerprint,
|
||||
Gift,
|
||||
Hammer,
|
||||
HelpCircle,
|
||||
Laptop2,
|
||||
Layers,
|
||||
LayoutDashboard,
|
||||
Lightbulb,
|
||||
Link,
|
||||
MessageCircle,
|
||||
Paperclip,
|
||||
Rocket,
|
||||
Scissors,
|
||||
TerminalSquare,
|
||||
Wand2,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import { SupabaseIcon } from "./icons/supabase";
|
||||
import { MongoDBIcon } from "./icons/MongoDB";
|
||||
|
||||
export function classNames(...classes: Array<string>) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
|
|
@ -170,6 +194,9 @@ export const nodeIcons: {
|
|||
ChatOpenAI: OpenAiIcon,
|
||||
OpenAI: OpenAiIcon,
|
||||
OpenAIEmbeddings: OpenAiIcon,
|
||||
Pinecone: PineconeIcon,
|
||||
SupabaseVectorStore: SupabaseIcon,
|
||||
MongoDBAtlasVectorSearch: MongoDBIcon,
|
||||
// UnstructuredPowerPointLoader: PowerPointIcon, // word and powerpoint have differente styles
|
||||
Qdrant: QDrantIcon,
|
||||
// ReadTheDocsLoader: ReadTheDocsIcon, // does not work
|
||||
|
|
@ -198,6 +225,142 @@ export const nodeIcons: {
|
|||
unknown: QuestionMarkCircleIcon,
|
||||
};
|
||||
|
||||
export const nodeIconsLucide: {
|
||||
[char: string]: React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>;
|
||||
} = {
|
||||
Chroma: ChromaIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
AirbyteJSONLoader: AirbyteIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Anthropic: AnthropicIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
ChatAnthropic: AnthropicIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
BingSearchAPIWrapper: BingIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
BingSearchRun: BingIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Cohere: CohereIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
CohereEmbeddings: CohereIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
EverNoteLoader: EvernoteIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
FacebookChatLoader: FBIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
GitbookLoader: GitBookIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
GoogleSearchAPIWrapper: GoogleIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
GoogleSearchResults: GoogleIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
GoogleSearchRun: GoogleIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
HNLoader: HackerNewsIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
HuggingFaceHub: HugginFaceIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
HuggingFaceEmbeddings: HugginFaceIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
IFixitLoader: IFixIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Meta: MetaIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Midjorney: MidjorneyIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
NotionDirectoryLoader: NotionIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
ChatOpenAI: OpenAiIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
OpenAI: OpenAiIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
OpenAIEmbeddings: OpenAiIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Qdrant: QDrantIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
Searx: SearxIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
SlackDirectoryLoader: SlackIcon as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
agents: Rocket as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
chains: Link as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
memories: Cpu as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
llms: Lightbulb as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
prompts: TerminalSquare as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
tools: Wrench as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
advanced: Laptop2 as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
chat: MessageCircle as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
embeddings: Fingerprint as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
documentloaders: Paperclip as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
vectorstores: Layers as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
toolkits: Hammer as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
textsplitters: Scissors as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
wrappers: Gift as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
utilities: Wand2 as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
unknown: HelpCircle as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
};
|
||||
|
||||
export const gradients = [
|
||||
"bg-gradient-to-br from-gray-800 via-rose-700 to-violet-900",
|
||||
"bg-gradient-to-br from-green-200 via-green-300 to-blue-500",
|
||||
|
|
@ -403,9 +566,7 @@ export function toFirstUpperCase(str: string) {
|
|||
}
|
||||
|
||||
export function snakeToSpaces(str: string) {
|
||||
let result = str.split("_").join(" ");
|
||||
|
||||
return result;
|
||||
return str.split("_").join(" ");
|
||||
}
|
||||
|
||||
export function toNormalCase(str: string) {
|
||||
|
|
@ -449,10 +610,7 @@ export function roundNumber(x: number, decimals: number) {
|
|||
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
|
||||
const sourceId = edge.source;
|
||||
const targetId = edge.target;
|
||||
const connectedNodes = nodes.filter(
|
||||
(node) => node.id === targetId || node.id === sourceId
|
||||
);
|
||||
return connectedNodes;
|
||||
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
|
||||
}
|
||||
|
||||
export function isValidConnection(
|
||||
|
|
@ -699,7 +857,7 @@ export function groupByFamily(data, baseClasses) {
|
|||
return foundIndex === index;
|
||||
});
|
||||
|
||||
let groupedObj = groupedBy.reduce((result, item) => {
|
||||
return groupedBy.reduce((result, item) => {
|
||||
const existingGroup = result.find((group) => group.family === item.family);
|
||||
|
||||
if (existingGroup) {
|
||||
|
|
@ -710,8 +868,6 @@ export function groupByFamily(data, baseClasses) {
|
|||
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
return groupedObj;
|
||||
}
|
||||
|
||||
export function buildTweaks(flow) {
|
||||
|
|
@ -752,9 +908,7 @@ export function validateNode(
|
|||
)
|
||||
? [
|
||||
`${type} is missing ${
|
||||
template.display_name
|
||||
? template.display_name
|
||||
: toNormalCase(template[t].name)
|
||||
template.display_name || toNormalCase(template[t].name)
|
||||
}.`,
|
||||
]
|
||||
: []
|
||||
|
|
@ -812,3 +966,10 @@ export function getRandomName(
|
|||
// Return title case final name
|
||||
return toTitleCase(final_name);
|
||||
}
|
||||
|
||||
export function getRandomKeyByssmm(): string {
|
||||
const now = new Date();
|
||||
const seconds = String(now.getSeconds()).padStart(2, "0");
|
||||
const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
|
||||
return seconds + milliseconds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,5 +241,6 @@ module.exports = {
|
|||
});
|
||||
}),
|
||||
require("@tailwindcss/typography"),
|
||||
require("daisyui"),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -97,7 +97,7 @@ def test_csv_agent(client: TestClient):
|
|||
"name": "path",
|
||||
"type": "file",
|
||||
"list": False,
|
||||
"content": None,
|
||||
"file_path": None,
|
||||
"advanced": False,
|
||||
}
|
||||
assert template["llm"] == {
|
||||
|
|
@ -137,6 +137,7 @@ def test_initialize_agent(client: TestClient):
|
|||
"self-ask-with-search",
|
||||
"conversational-react-description",
|
||||
"openai-functions",
|
||||
"openai-multi-functions",
|
||||
],
|
||||
"name": "agent",
|
||||
"type": "str",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ def test_template_field_defaults(sample_template_field: TemplateField):
|
|||
assert sample_template_field.value is None
|
||||
assert sample_template_field.suffixes == []
|
||||
assert sample_template_field.file_types == []
|
||||
assert sample_template_field.content is None
|
||||
assert sample_template_field.file_path is None
|
||||
assert sample_template_field.password is False
|
||||
assert sample_template_field.name == "test_field"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from typing import Type, Union
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
|
|
@ -236,6 +238,7 @@ def test_build_params(basic_graph):
|
|||
root = get_root_node(basic_graph)
|
||||
# Root node is a TimeTravelGuideChain
|
||||
# which requires an llm and memory
|
||||
assert root is not None
|
||||
assert isinstance(root.params, dict)
|
||||
assert "llm" in root.params
|
||||
assert "memory" in root.params
|
||||
|
|
@ -269,7 +272,6 @@ def test_tool_node_build(complex_graph):
|
|||
assert tool_node is not None
|
||||
built_object = tool_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the ToolNode's build() method
|
||||
|
||||
|
||||
def test_chain_node_build(complex_graph):
|
||||
|
|
@ -277,7 +279,6 @@ def test_chain_node_build(complex_graph):
|
|||
assert chain_node is not None
|
||||
built_object = chain_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the ChainNode's build() method
|
||||
|
||||
|
||||
def test_prompt_node_build(complex_graph):
|
||||
|
|
@ -285,7 +286,6 @@ def test_prompt_node_build(complex_graph):
|
|||
assert prompt_node is not None
|
||||
built_object = prompt_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the PromptNode's build() method
|
||||
|
||||
|
||||
def test_llm_node_build(basic_graph):
|
||||
|
|
@ -293,23 +293,36 @@ def test_llm_node_build(basic_graph):
|
|||
assert llm_node is not None
|
||||
built_object = llm_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the LLMNode's build() method
|
||||
|
||||
|
||||
def test_toolkit_node_build(openapi_graph):
|
||||
# Write a file to the disk
|
||||
file_path = "api-with-examples.yaml"
|
||||
with open(file_path, "w") as f:
|
||||
f.write("openapi: 3.0.0")
|
||||
|
||||
toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
|
||||
assert toolkit_node is not None
|
||||
built_object = toolkit_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the ToolkitNode's build() method
|
||||
# Remove the file
|
||||
os.remove(file_path)
|
||||
assert not Path(file_path).exists()
|
||||
|
||||
|
||||
def test_file_tool_node_build(openapi_graph):
|
||||
file_path = "api-with-examples.yaml"
|
||||
with open(file_path, "w") as f:
|
||||
f.write("openapi: 3.0.0")
|
||||
|
||||
assert Path(file_path).exists()
|
||||
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
|
||||
assert file_tool_node is not None
|
||||
built_object = file_tool_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the FileToolNode's build() method
|
||||
# Remove the file
|
||||
os.remove(file_path)
|
||||
assert not Path(file_path).exists()
|
||||
|
||||
|
||||
def test_wrapper_node_build(openapi_graph):
|
||||
|
|
@ -317,7 +330,6 @@ def test_wrapper_node_build(openapi_graph):
|
|||
assert wrapper_node is not None
|
||||
built_object = wrapper_node.build()
|
||||
assert built_object is not None
|
||||
# Add any further assertions specific to the WrapperNode's build() method
|
||||
|
||||
|
||||
def test_get_result_and_thought(basic_graph):
|
||||
|
|
@ -325,7 +337,7 @@ def test_get_result_and_thought(basic_graph):
|
|||
responses = [
|
||||
"Final Answer: I am a response",
|
||||
]
|
||||
message = "Hello"
|
||||
message = {"input": "Hello"}
|
||||
# Find the node that is an LLMNode and change the
|
||||
# _built_object to a FakeListLLM
|
||||
llm_node = get_node_by_type(basic_graph, LLMVertex)
|
||||
|
|
@ -338,8 +350,5 @@ def test_get_result_and_thought(basic_graph):
|
|||
# now build again and check if FakeListLLM was used
|
||||
|
||||
# Get the result and thought
|
||||
result, thought = get_result_and_thought(langchain_object, message)
|
||||
# The result should be a str
|
||||
assert isinstance(result, str)
|
||||
# The thought should be a Thought
|
||||
assert isinstance(thought, str)
|
||||
result = get_result_and_thought(langchain_object, message)
|
||||
assert isinstance(result, dict)
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ def test_chat_open_ai(client: TestClient):
|
|||
"placeholder": "",
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "gpt-3.5-turbo",
|
||||
"value": "gpt-3.5-turbo-0613",
|
||||
"password": False,
|
||||
"options": [
|
||||
"gpt-3.5-turbo-0613",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ def test_build_template_from_function():
|
|||
# Test with valid name
|
||||
result = build_template_from_function("ExampleClass1", type_to_loader_dict)
|
||||
|
||||
assert result is not None
|
||||
assert "template" in result
|
||||
assert "description" in result
|
||||
assert "base_classes" in result
|
||||
|
|
@ -67,6 +68,7 @@ def test_build_template_from_function():
|
|||
result_with_function = build_template_from_function(
|
||||
"ExampleClass1", type_to_loader_dict, add_function=True
|
||||
)
|
||||
assert result_with_function is not None
|
||||
assert "function" in result_with_function["base_classes"]
|
||||
|
||||
# Test with invalid name
|
||||
|
|
@ -80,6 +82,7 @@ def test_build_template_from_class():
|
|||
|
||||
# Test valid input
|
||||
result = build_template_from_class("Child", type_to_cls_dict)
|
||||
assert result is not None
|
||||
assert "template" in result
|
||||
assert "description" in result
|
||||
assert "base_classes" in result
|
||||
|
|
@ -222,6 +225,7 @@ def test_format_dict():
|
|||
"password": False,
|
||||
"multiline": False,
|
||||
"options": OPENAI_MODELS,
|
||||
"value": "text-davinci-003",
|
||||
},
|
||||
}
|
||||
expected_output_openai_chat = {
|
||||
|
|
@ -233,6 +237,7 @@ def test_format_dict():
|
|||
"password": False,
|
||||
"multiline": False,
|
||||
"options": CHAT_OPENAI_MODELS,
|
||||
"value": "gpt-3.5-turbo-0613",
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict, "OpenAI") == expected_output_openai
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue