Merge remote-tracking branch 'origin/feature/store' into bug/undo-copy
This commit is contained in:
commit
7ee1ad03f4
101 changed files with 1925 additions and 919 deletions
|
|
@ -313,7 +313,7 @@ def superuser(
|
|||
|
||||
if create_super_user(db=session, username=username, password=password):
|
||||
# Verify that the superuser was created
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
|
||||
user: User = session.query(User).filter(User.username == username).first()
|
||||
if user is None or not user.is_superuser:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
"""Adds Credential table
|
||||
|
||||
Revision ID: c1c8e217a069
|
||||
Revises: 7d2162acc8b2
|
||||
Create Date: 2023-11-24 10:45:38.465302
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '2ac71eb9c3ae'
|
||||
down_revision: Union[str, None] = '7d2162acc8b2'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
try:
|
||||
op.create_table('credential',
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('value', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('provider', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('user_id', sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||
sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
try:
|
||||
op.drop_table('credential')
|
||||
except Exception:
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
"""Fix Credential table
|
||||
|
||||
Revision ID: fd531f8868b1
|
||||
Revises: 2ac71eb9c3ae
|
||||
Create Date: 2023-11-24 15:07:37.566516
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'fd531f8868b1'
|
||||
down_revision: Union[str, None] = '2ac71eb9c3ae'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
try:
|
||||
with op.batch_alter_table('credential', schema=None) as batch_op:
|
||||
batch_op.create_foreign_key("fk_credential_user_id", 'user', ['user_id'], ['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
try:
|
||||
with op.batch_alter_table('credential', schema=None) as batch_op:
|
||||
batch_op.drop_constraint("fk_credential_user_id", type_='foreignkey')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
# Router for base api
|
||||
from fastapi import APIRouter
|
||||
|
||||
from langflow.api.v1 import (
|
||||
api_key_router,
|
||||
chat_router,
|
||||
credentials_router,
|
||||
endpoints_router,
|
||||
validate_router,
|
||||
flows_router,
|
||||
login_router,
|
||||
store_router,
|
||||
users_router,
|
||||
api_key_router,
|
||||
login_router,
|
||||
validate_router,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
|
|
@ -22,3 +24,4 @@ router.include_router(flows_router)
|
|||
router.include_router(users_router)
|
||||
router.include_router(api_key_router)
|
||||
router.include_router(login_router)
|
||||
router.include_router(credentials_router)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
from langflow.api.v1.endpoints import router as endpoints_router
|
||||
from langflow.api.v1.validate import router as validate_router
|
||||
from langflow.api.v1.api_key import router as api_key_router
|
||||
from langflow.api.v1.chat import router as chat_router
|
||||
from langflow.api.v1.credential import router as credentials_router
|
||||
from langflow.api.v1.endpoints import router as endpoints_router
|
||||
from langflow.api.v1.flows import router as flows_router
|
||||
from langflow.api.v1.login import router as login_router
|
||||
from langflow.api.v1.store import router as store_router
|
||||
from langflow.api.v1.users import router as users_router
|
||||
from langflow.api.v1.api_key import router as api_key_router
|
||||
from langflow.api.v1.login import router as login_router
|
||||
from langflow.api.v1.validate import router as validate_router
|
||||
|
||||
__all__ = [
|
||||
"chat_router",
|
||||
|
|
@ -16,4 +17,5 @@ __all__ = [
|
|||
"users_router",
|
||||
"api_key_router",
|
||||
"login_router",
|
||||
"credentials_router",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
from typing import TYPE_CHECKING
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from langflow.api.v1.schemas import ApiKeysResponse, ApiKeyCreateRequest
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.api.v1.schemas import ApiKeyCreateRequest, ApiKeysResponse
|
||||
from langflow.services.auth import utils as auth_utils
|
||||
from langflow.services.database.models.api_key.api_key import (
|
||||
ApiKeyCreate,
|
||||
UnmaskedApiKeyRead,
|
||||
)
|
||||
|
||||
# Assuming you have these methods in your service layer
|
||||
from langflow.services.database.models.api_key.crud import (
|
||||
get_api_keys,
|
||||
create_api_key,
|
||||
delete_api_key,
|
||||
get_api_keys,
|
||||
)
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.api_key.model import (
|
||||
ApiKeyCreate,
|
||||
UnmaskedApiKeyRead,
|
||||
)
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import (
|
||||
get_session,
|
||||
get_settings_service,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
|
@ -85,3 +85,16 @@ def save_store_api_key(
|
|||
return {"detail": "API Key saved"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.delete("/store")
|
||||
def delete_store_api_key(
|
||||
current_user: User = Depends(auth_utils.get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
try:
|
||||
current_user.store_api_key = None
|
||||
db.commit()
|
||||
return {"detail": "API Key deleted"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
|
|
|||
|
|
@ -1,27 +1,15 @@
|
|||
from fastapi import (
|
||||
APIRouter,
|
||||
Depends,
|
||||
HTTPException,
|
||||
Query,
|
||||
WebSocket,
|
||||
WebSocketException,
|
||||
status,
|
||||
)
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketException, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from loguru import logger
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.api.utils import build_input_keys_response
|
||||
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.services.auth.utils import (
|
||||
get_current_active_user,
|
||||
get_current_user_by_jwt,
|
||||
)
|
||||
from langflow.services.auth.utils import get_current_active_user, get_current_user_by_jwt
|
||||
from langflow.services.cache.service import BaseCacheService
|
||||
from langflow.services.cache.utils import update_build_status
|
||||
from langflow.services.chat.service import ChatService
|
||||
from langflow.services.deps import get_cache_service, get_chat_service, get_session
|
||||
from loguru import logger
|
||||
from sqlmodel import Session
|
||||
|
||||
router = APIRouter(tags=["Chat"])
|
||||
|
||||
|
|
@ -148,7 +136,7 @@ async def stream_build(
|
|||
# Some error could happen when building the graph
|
||||
graph = Graph.from_payload(graph_data)
|
||||
|
||||
number_of_nodes = len(graph.nodes)
|
||||
number_of_nodes = len(graph.vertices)
|
||||
update_build_status(cache_service, flow_id, BuildStatus.IN_PROGRESS)
|
||||
|
||||
try:
|
||||
|
|
|
|||
90
src/backend/langflow/api/v1/credential.py
Normal file
90
src/backend/langflow/api/v1/credential.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from langflow.services.auth import utils as auth_utils
|
||||
from langflow.services.auth.utils import get_current_active_user
|
||||
from langflow.services.database.models.credential import Credential, CredentialCreate, CredentialRead, CredentialUpdate
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_session, get_settings_service
|
||||
from sqlmodel import Session
|
||||
|
||||
router = APIRouter(prefix="/credentials", tags=["Credentials"])
|
||||
|
||||
|
||||
@router.post("/", response_model=CredentialRead, status_code=201)
|
||||
def create_credential(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
credential: CredentialCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
"""Create a new credential."""
|
||||
try:
|
||||
# check if credential name already exists
|
||||
credential_exists = (
|
||||
session.query(Credential)
|
||||
.filter(Credential.name == credential.name, Credential.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if credential_exists:
|
||||
raise HTTPException(status_code=400, detail="Credential name already exists")
|
||||
|
||||
db_credential = Credential.model_validate(credential, from_attributes=True)
|
||||
if not db_credential.value:
|
||||
raise HTTPException(status_code=400, detail="Credential value cannot be empty")
|
||||
encrypted = auth_utils.encrypt_api_key(db_credential.value, settings_service=settings_service)
|
||||
db_credential.value = encrypted
|
||||
db_credential.user_id = current_user.id
|
||||
session.add(db_credential)
|
||||
session.commit()
|
||||
session.refresh(db_credential)
|
||||
return db_credential
|
||||
except Exception as e:
|
||||
if isinstance(e, HTTPException):
|
||||
raise e
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.get("/", response_model=list[CredentialRead], status_code=200)
|
||||
def read_credentials(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Read all credentials."""
|
||||
try:
|
||||
credentials = session.query(Credential).filter(Credential.user_id == current_user.id).all()
|
||||
return credentials
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.patch("/{credential_id}", response_model=CredentialRead, status_code=200)
|
||||
def update_credential(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
credential_id: UUID,
|
||||
credential: CredentialUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Update a credential."""
|
||||
try:
|
||||
db_credential = (
|
||||
session.query(Credential)
|
||||
.filter(Credential.id == credential_id, Credential.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if not db_credential:
|
||||
raise HTTPException(status_code=404, detail="Credential not found")
|
||||
|
||||
credential_data = credential.model_dump(exclude_unset=True)
|
||||
for key, value in credential_data.items():
|
||||
setattr(db_credential, key, value)
|
||||
db_credential.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
session.refresh(db_credential)
|
||||
return db_credential
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
|
@ -3,8 +3,6 @@ from typing import Annotated, Optional, Union
|
|||
|
||||
import sqlalchemy as sa
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.v1.schemas import (
|
||||
CustomComponentCode,
|
||||
ProcessResponse,
|
||||
|
|
@ -14,17 +12,14 @@ from langflow.api.v1.schemas import (
|
|||
)
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||
from langflow.interface.types import build_langchain_template_custom_component, create_and_validate_component
|
||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||
from langflow.services.auth.utils import api_key_security, get_current_active_user
|
||||
from langflow.services.cache.utils import save_uploaded_file
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.deps import (
|
||||
get_session,
|
||||
get_session_service,
|
||||
get_settings_service,
|
||||
get_task_service,
|
||||
)
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service
|
||||
from loguru import logger
|
||||
|
||||
try:
|
||||
from langflow.worker import process_graph_cached_task
|
||||
|
|
@ -34,9 +29,8 @@ except ImportError:
|
|||
raise NotImplementedError("Celery is not installed")
|
||||
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.services.task.service import TaskService
|
||||
from sqlmodel import Session
|
||||
|
||||
# build router
|
||||
router = APIRouter(tags=["Base"])
|
||||
|
|
@ -208,9 +202,7 @@ async def custom_component(
|
|||
raw_code: CustomComponentCode,
|
||||
user: User = Depends(get_current_active_user),
|
||||
):
|
||||
from langflow.interface.types import (
|
||||
build_langchain_template_custom_component,
|
||||
)
|
||||
component = create_and_validate_component(raw_code.code)
|
||||
|
||||
extractor = CustomComponent(code=raw_code.code)
|
||||
extractor.validate()
|
||||
|
|
@ -219,10 +211,8 @@ async def custom_component(
|
|||
|
||||
|
||||
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||
async def reload_custom_component(path: str):
|
||||
from langflow.interface.types import (
|
||||
build_langchain_template_custom_component,
|
||||
)
|
||||
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
|
||||
from langflow.interface.types import build_langchain_template_custom_component
|
||||
|
||||
try:
|
||||
reader = DirectoryReader("")
|
||||
|
|
@ -235,3 +225,15 @@ async def reload_custom_component(path: str):
|
|||
return build_langchain_template_custom_component(extractor, user_id=user.id)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@router.post("/custom_component/update", status_code=HTTPStatus.OK)
|
||||
async def custom_component_update(
|
||||
raw_code: CustomComponentCode,
|
||||
user: User = Depends(get_current_active_user),
|
||||
):
|
||||
component = create_and_validate_component(raw_code.code)
|
||||
|
||||
component_node = build_langchain_template_custom_component(component, user_id=user.id, update_field=raw_code.field)
|
||||
# Update the field
|
||||
return component_node
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from langflow.api.utils import remove_api_keys
|
|||
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
||||
from langflow.services.auth.utils import get_current_active_user
|
||||
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_session, get_settings_service
|
||||
|
||||
# build router
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ from enum import Enum
|
|||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from uuid import UUID
|
||||
from langflow.services.database.models.api_key.api_key import ApiKeyRead
|
||||
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||
from langflow.services.database.models.user import UserRead
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from langflow.services.database.models.api_key.model import ApiKeyRead
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||
from langflow.services.database.models.user import UserRead
|
||||
|
||||
|
||||
class BuildStatus(Enum):
|
||||
"""Status of the build."""
|
||||
|
|
@ -156,6 +157,7 @@ class StreamData(BaseModel):
|
|||
|
||||
class CustomComponentCode(BaseModel):
|
||||
code: str
|
||||
field: Optional[str] = None
|
||||
|
||||
|
||||
class CustomComponentResponseError(BaseModel):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from uuid import UUID
|
|||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from langflow.services.auth import utils as auth_utils
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_settings_service, get_store_service
|
||||
from langflow.services.store.exceptions import CustomException
|
||||
from langflow.services.store.schema import (
|
||||
|
|
|
|||
37
src/backend/langflow/components/agents/AgentInitializer.py
Normal file
37
src/backend/langflow/components/agents/AgentInitializer.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from typing import Callable, List, Union
|
||||
|
||||
from langchain.agents import AgentExecutor, AgentType, initialize_agent, types
|
||||
|
||||
from langflow import CustomComponent
|
||||
from langflow.field_typing import BaseChatMemory, BaseLanguageModel, Tool
|
||||
|
||||
|
||||
class AgentInitializerComponent(CustomComponent):
|
||||
display_name: str = "Agent Initializer"
|
||||
description: str = "Initialize a Langchain Agent."
|
||||
documentation: str = "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||
|
||||
def build_config(self):
|
||||
agents = list(types.AGENT_TO_CLASS.keys())
|
||||
# field_type and required are optional
|
||||
return {
|
||||
"agent": {"options": agents, "value": agents[0], "display_name": "Agent Type"},
|
||||
"max_iterations": {"display_name": "Max Iterations", "value": 10},
|
||||
"memory": {"display_name": "Memory"},
|
||||
"tools": {"display_name": "Tools"},
|
||||
"llm": {"display_name": "Language Model"},
|
||||
}
|
||||
|
||||
def build(
|
||||
self, agent: str, llm: BaseLanguageModel, memory: BaseChatMemory, tools: List[Tool], max_iterations: int
|
||||
) -> Union[AgentExecutor, Callable]:
|
||||
agent = AgentType(agent)
|
||||
return initialize_agent(
|
||||
tools=tools,
|
||||
llm=llm,
|
||||
agent=agent,
|
||||
memory=memory,
|
||||
return_intermediate_steps=True,
|
||||
handle_parsing_errors=True,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
from langflow import CustomComponent
|
||||
from typing import Callable, Optional, Union
|
||||
|
||||
from langchain.chains import LLMChain
|
||||
from typing import Optional, Union, Callable
|
||||
|
||||
from langflow import CustomComponent
|
||||
from langflow.field_typing import (
|
||||
BasePromptTemplate,
|
||||
BaseLanguageModel,
|
||||
BaseMemory,
|
||||
BasePromptTemplate,
|
||||
Chain,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ agents:
|
|||
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi"
|
||||
CSVAgent:
|
||||
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv"
|
||||
AgentInitializer:
|
||||
documentation: "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||
VectorStoreAgent:
|
||||
documentation: ""
|
||||
VectorStoreRouterAgent:
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ from typing import Callable, Dict, Union
|
|||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.document_loaders.base import BaseLoader
|
||||
from langchain.llms.base import BaseLanguageModel, BaseLLM
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.memory.chat_memory import BaseChatMemory
|
||||
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
from langchain.schema import BaseOutputParser, BaseRetriever, Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.memory import BaseMemory
|
||||
from langchain.text_splitter import TextSplitter
|
||||
from langchain.tools import Tool
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from typing import TYPE_CHECKING
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
|
|
@ -22,8 +22,8 @@ class TargetHandle(BaseModel):
|
|||
|
||||
class Edge:
|
||||
def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
|
||||
self.source: "Vertex" = source
|
||||
self.target: "Vertex" = target
|
||||
self.source_id: str = source.id
|
||||
self.target_id: str = target.id
|
||||
if data := edge.get("data", {}):
|
||||
self._source_handle = data.get("sourceHandle", {})
|
||||
self._target_handle = data.get("targetHandle", {})
|
||||
|
|
@ -31,7 +31,7 @@ class Edge:
|
|||
self.target_handle: TargetHandle = TargetHandle(**self._target_handle)
|
||||
self.target_param = self.target_handle.fieldName
|
||||
# validate handles
|
||||
self.validate_handles()
|
||||
self.validate_handles(source, target)
|
||||
else:
|
||||
# Logging here because this is a breaking change
|
||||
logger.error("Edge data is empty")
|
||||
|
|
@ -41,9 +41,9 @@ class Edge:
|
|||
# target_param is documents
|
||||
self.target_param = self._target_handle.split("|")[1]
|
||||
# Validate in __init__ to fail fast
|
||||
self.validate_edge()
|
||||
self.validate_edge(source, target)
|
||||
|
||||
def validate_handles(self) -> None:
|
||||
def validate_handles(self, source, target) -> None:
|
||||
if self.target_handle.inputTypes is None:
|
||||
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
|
||||
else:
|
||||
|
|
@ -54,26 +54,20 @@ class Edge:
|
|||
if not self.valid_handles:
|
||||
logger.debug(self.source_handle)
|
||||
logger.debug(self.target_handle)
|
||||
raise ValueError(
|
||||
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has invalid handles"
|
||||
)
|
||||
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.source = state["source"]
|
||||
self.target = state["target"]
|
||||
self.source_id = state["source_id"]
|
||||
self.target_id = state["target_id"]
|
||||
self.target_param = state["target_param"]
|
||||
self.source_handle = state.get("source_handle")
|
||||
self.target_handle = state.get("target_handle")
|
||||
|
||||
def reset(self) -> None:
|
||||
self.source._build_params()
|
||||
self.target._build_params()
|
||||
|
||||
def validate_edge(self) -> None:
|
||||
def validate_edge(self, source, target) -> None:
|
||||
# Validate that the outputs of the source node are valid inputs
|
||||
# for the target node
|
||||
self.source_types = self.source.output
|
||||
self.target_reqs = self.target.required_inputs + self.target.optional_inputs
|
||||
self.source_types = source.output
|
||||
self.target_reqs = target.required_inputs + target.optional_inputs
|
||||
# Both lists contain strings and sometimes a string contains the value we are
|
||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||
# so we need to check if any of the strings in source_types is in target_reqs
|
||||
|
|
@ -88,13 +82,11 @@ class Edge:
|
|||
if no_matched_type:
|
||||
logger.debug(self.source_types)
|
||||
logger.debug(self.target_reqs)
|
||||
raise ValueError(
|
||||
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has no matched type"
|
||||
)
|
||||
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Edge(source={self.source.id}, target={self.target.id}, target_param={self.target_param}"
|
||||
f"Edge(source={self.source_id}, target={self.target_id}, target_param={self.target_param}"
|
||||
f", matched_type={self.matched_type})"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,32 +13,32 @@ from langflow.utils import payload
|
|||
|
||||
|
||||
class Graph:
|
||||
"""A class representing a graph of nodes and edges."""
|
||||
"""A class representing a graph of vertices and edges."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nodes: List[Dict],
|
||||
edges: List[Dict[str, str]],
|
||||
) -> None:
|
||||
self._nodes = nodes
|
||||
self._vertices = nodes
|
||||
self._edges = edges
|
||||
self.raw_graph_data = {"nodes": nodes, "edges": edges}
|
||||
|
||||
self.top_level_nodes = []
|
||||
for node in self._nodes:
|
||||
if node_id := node.get("id"):
|
||||
self.top_level_nodes.append(node_id)
|
||||
self.top_level_vertices = []
|
||||
for vertex in self._vertices:
|
||||
if vertex_id := vertex.get("id"):
|
||||
self.top_level_vertices.append(vertex_id)
|
||||
self._graph_data = process_flow(self.raw_graph_data)
|
||||
|
||||
self._nodes = self._graph_data["nodes"]
|
||||
self._vertices = self._graph_data["nodes"]
|
||||
self._edges = self._graph_data["edges"]
|
||||
self._build_graph()
|
||||
|
||||
def __getstate__(self):
|
||||
return self.raw_graph_data
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
for edge in self.edges:
|
||||
edge.reset()
|
||||
edge.validate_edge()
|
||||
self.__init__(**state)
|
||||
|
||||
@classmethod
|
||||
def from_payload(cls, payload: Dict) -> "Graph":
|
||||
|
|
@ -54,9 +54,9 @@ class Graph:
|
|||
if "data" in payload:
|
||||
payload = payload["data"]
|
||||
try:
|
||||
nodes = payload["nodes"]
|
||||
vertices = payload["nodes"]
|
||||
edges = payload["edges"]
|
||||
return cls(nodes, edges)
|
||||
return cls(vertices, edges)
|
||||
except KeyError as exc:
|
||||
logger.exception(exc)
|
||||
raise ValueError(
|
||||
|
|
@ -69,61 +69,69 @@ class Graph:
|
|||
return self.__repr__() == other.__repr__()
|
||||
|
||||
def _build_graph(self) -> None:
|
||||
"""Builds the graph from the nodes and edges."""
|
||||
self.nodes = self._build_vertices()
|
||||
"""Builds the graph from the vertices and edges."""
|
||||
self.vertices = self._build_vertices()
|
||||
self.vertex_ids = [vertex.id for vertex in self.vertices]
|
||||
self.edges = self._build_edges()
|
||||
for edge in self.edges:
|
||||
edge.source.add_edge(edge)
|
||||
edge.target.add_edge(edge)
|
||||
|
||||
# This is a hack to make sure that the LLM node is sent to
|
||||
# the toolkit node
|
||||
self._build_node_params()
|
||||
# remove invalid nodes
|
||||
self._validate_nodes()
|
||||
# This is a hack to make sure that the LLM vertex is sent to
|
||||
# the toolkit vertex
|
||||
self._build_vertex_params()
|
||||
# remove invalid vertices
|
||||
self._validate_vertices()
|
||||
|
||||
def _build_node_params(self) -> None:
|
||||
"""Identifies and handles the LLM node within the graph."""
|
||||
llm_node = None
|
||||
for node in self.nodes:
|
||||
node._build_params()
|
||||
if isinstance(node, LLMVertex):
|
||||
llm_node = node
|
||||
def _build_vertex_params(self) -> None:
|
||||
"""Identifies and handles the LLM vertex within the graph."""
|
||||
llm_vertex = None
|
||||
for vertex in self.vertices:
|
||||
vertex._build_params()
|
||||
if isinstance(vertex, LLMVertex):
|
||||
llm_vertex = vertex
|
||||
|
||||
if llm_node:
|
||||
for node in self.nodes:
|
||||
if isinstance(node, ToolkitVertex):
|
||||
node.params["llm"] = llm_node
|
||||
if llm_vertex:
|
||||
for vertex in self.vertices:
|
||||
if isinstance(vertex, ToolkitVertex):
|
||||
vertex.params["llm"] = llm_vertex
|
||||
|
||||
def _validate_nodes(self) -> None:
|
||||
"""Check that all nodes have edges"""
|
||||
if len(self.nodes) == 1:
|
||||
def _validate_vertices(self) -> None:
|
||||
"""Check that all vertices have edges"""
|
||||
if len(self.vertices) == 1:
|
||||
return
|
||||
for node in self.nodes:
|
||||
if not self._validate_node(node):
|
||||
raise ValueError(f"{node.vertex_type} is not connected to any other components")
|
||||
for vertex in self.vertices:
|
||||
if not self._validate_vertex(vertex):
|
||||
raise ValueError(f"{vertex.vertex_type} is not connected to any other components")
|
||||
|
||||
def _validate_node(self, node: Vertex) -> bool:
|
||||
"""Validates a node."""
|
||||
# All nodes that do not have edges are invalid
|
||||
return len(node.edges) > 0
|
||||
def _validate_vertex(self, vertex: Vertex) -> bool:
|
||||
"""Validates a vertex."""
|
||||
# All vertices that do not have edges are invalid
|
||||
return len(self.get_vertex_edges(vertex.id)) > 0
|
||||
|
||||
def get_node(self, node_id: str) -> Union[None, Vertex]:
|
||||
"""Returns a node by id."""
|
||||
return next((node for node in self.nodes if node.id == node_id), None)
|
||||
def get_vertex(self, vertex_id: str) -> Union[None, Vertex]:
|
||||
"""Returns a vertex by id."""
|
||||
return next((vertex for vertex in self.vertices if vertex.id == vertex_id), None)
|
||||
|
||||
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
||||
"""Returns the nodes connected to a node."""
|
||||
connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
|
||||
return connected_nodes
|
||||
def get_vertex_edges(self, vertex_id: str) -> List[Edge]:
|
||||
"""Returns a list of edges for a given vertex."""
|
||||
return [edge for edge in self.edges if edge.source_id == vertex_id or edge.target_id == vertex_id]
|
||||
|
||||
def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]:
|
||||
"""Returns the vertices connected to a vertex."""
|
||||
vertices: List[Vertex] = []
|
||||
for edge in self.edges:
|
||||
if edge.target_id == vertex_id:
|
||||
vertex = self.get_vertex(edge.source_id)
|
||||
if vertex is None:
|
||||
continue
|
||||
vertices.append(vertex)
|
||||
return vertices
|
||||
|
||||
async def build(self) -> Chain:
|
||||
"""Builds the graph."""
|
||||
# Get root node
|
||||
root_node = payload.get_root_node(self)
|
||||
if root_node is None:
|
||||
raise ValueError("No root node found")
|
||||
return await root_node.build()
|
||||
# Get root vertex
|
||||
root_vertex = payload.get_root_vertex(self)
|
||||
if root_vertex is None:
|
||||
raise ValueError("No root vertex found")
|
||||
return await root_vertex.build()
|
||||
|
||||
def topological_sort(self) -> List[Vertex]:
|
||||
"""
|
||||
|
|
@ -136,25 +144,25 @@ class Graph:
|
|||
ValueError: If the graph contains a cycle.
|
||||
"""
|
||||
# States: 0 = unvisited, 1 = visiting, 2 = visited
|
||||
state = {node: 0 for node in self.nodes}
|
||||
state = {vertex: 0 for vertex in self.vertices}
|
||||
sorted_vertices = []
|
||||
|
||||
def dfs(node):
|
||||
if state[node] == 1:
|
||||
def dfs(vertex):
|
||||
if state[vertex] == 1:
|
||||
# We have a cycle
|
||||
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||
if state[node] == 0:
|
||||
state[node] = 1
|
||||
for edge in node.edges:
|
||||
if edge.source == node:
|
||||
if state[vertex] == 0:
|
||||
state[vertex] = 1
|
||||
for edge in vertex.edges:
|
||||
if edge.source == vertex:
|
||||
dfs(edge.target)
|
||||
state[node] = 2
|
||||
sorted_vertices.append(node)
|
||||
state[vertex] = 2
|
||||
sorted_vertices.append(vertex)
|
||||
|
||||
# Visit each node
|
||||
for node in self.nodes:
|
||||
if state[node] == 0:
|
||||
dfs(node)
|
||||
# Visit each vertex
|
||||
for vertex in self.vertices:
|
||||
if state[vertex] == 0:
|
||||
dfs(vertex)
|
||||
|
||||
return list(reversed(sorted_vertices))
|
||||
|
||||
|
|
@ -164,17 +172,21 @@ class Graph:
|
|||
logger.debug("There are %s vertices in the graph", len(sorted_vertices))
|
||||
yield from sorted_vertices
|
||||
|
||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||
"""Returns the neighbors of a node."""
|
||||
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
||||
"""Returns the neighbors of a vertex."""
|
||||
neighbors: Dict[Vertex, int] = {}
|
||||
for edge in self.edges:
|
||||
if edge.source == node:
|
||||
neighbor = edge.target
|
||||
if edge.source_id == vertex.id:
|
||||
neighbor = self.get_vertex(edge.target_id)
|
||||
if neighbor is None:
|
||||
continue
|
||||
if neighbor not in neighbors:
|
||||
neighbors[neighbor] = 0
|
||||
neighbors[neighbor] += 1
|
||||
elif edge.target == node:
|
||||
neighbor = edge.source
|
||||
elif edge.target_id == vertex.id:
|
||||
neighbor = self.get_vertex(edge.source_id)
|
||||
if neighbor is None:
|
||||
continue
|
||||
if neighbor not in neighbors:
|
||||
neighbors[neighbor] = 0
|
||||
neighbors[neighbor] += 1
|
||||
|
|
@ -182,59 +194,59 @@ class Graph:
|
|||
|
||||
def _build_edges(self) -> List[Edge]:
|
||||
"""Builds the edges of the graph."""
|
||||
# Edge takes two nodes as arguments, so we need to build the nodes first
|
||||
# Edge takes two vertices as arguments, so we need to build the vertices first
|
||||
# and then build the edges
|
||||
# if we can't find a node, we raise an error
|
||||
# if we can't find a vertex, we raise an error
|
||||
|
||||
edges: List[Edge] = []
|
||||
for edge in self._edges:
|
||||
source = self.get_node(edge["source"])
|
||||
target = self.get_node(edge["target"])
|
||||
source = self.get_vertex(edge["source"])
|
||||
target = self.get_vertex(edge["target"])
|
||||
if source is None:
|
||||
raise ValueError(f"Source node {edge['source']} not found")
|
||||
raise ValueError(f"Source vertex {edge['source']} not found")
|
||||
if target is None:
|
||||
raise ValueError(f"Target node {edge['target']} not found")
|
||||
raise ValueError(f"Target vertex {edge['target']} not found")
|
||||
edges.append(Edge(source, target, edge))
|
||||
return edges
|
||||
|
||||
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
|
||||
"""Returns the node class based on the node type."""
|
||||
if node_type in FILE_TOOLS:
|
||||
def _get_vertex_class(self, vertex_type: str, vertex_lc_type: str) -> Type[Vertex]:
|
||||
"""Returns the vertex class based on the vertex type."""
|
||||
if vertex_type in FILE_TOOLS:
|
||||
return FileToolVertex
|
||||
if node_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_type]
|
||||
if vertex_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[vertex_type]
|
||||
return (
|
||||
lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_lc_type]
|
||||
if node_lc_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP
|
||||
lazy_load_vertex_dict.VERTEX_TYPE_MAP[vertex_lc_type]
|
||||
if vertex_lc_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP
|
||||
else Vertex
|
||||
)
|
||||
|
||||
def _build_vertices(self) -> List[Vertex]:
|
||||
"""Builds the vertices of the graph."""
|
||||
nodes: List[Vertex] = []
|
||||
for node in self._nodes:
|
||||
node_data = node["data"]
|
||||
node_type: str = node_data["type"] # type: ignore
|
||||
node_lc_type: str = node_data["node"]["template"]["_type"] # type: ignore
|
||||
vertices: List[Vertex] = []
|
||||
for vertex in self._vertices:
|
||||
vertex_data = vertex["data"]
|
||||
vertex_type: str = vertex_data["type"] # type: ignore
|
||||
vertex_lc_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
||||
|
||||
VertexClass = self._get_vertex_class(node_type, node_lc_type)
|
||||
vertex = VertexClass(node)
|
||||
vertex.set_top_level(self.top_level_nodes)
|
||||
nodes.append(vertex)
|
||||
VertexClass = self._get_vertex_class(vertex_type, vertex_lc_type)
|
||||
vertex = VertexClass(vertex, graph=self)
|
||||
vertex.set_top_level(self.top_level_vertices)
|
||||
vertices.append(vertex)
|
||||
|
||||
return nodes
|
||||
return vertices
|
||||
|
||||
def get_children_by_node_type(self, node: Vertex, node_type: str) -> List[Vertex]:
|
||||
"""Returns the children of a node based on the node type."""
|
||||
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
||||
"""Returns the children of a vertex based on the vertex type."""
|
||||
children = []
|
||||
node_types = [node.data["type"]]
|
||||
if "node" in node.data:
|
||||
node_types += node.data["node"]["base_classes"]
|
||||
if node_type in node_types:
|
||||
children.append(node)
|
||||
vertex_types = [vertex.data["type"]]
|
||||
if "node" in vertex.data:
|
||||
vertex_types += vertex.data["node"]["base_classes"]
|
||||
if vertex_type in vertex_types:
|
||||
children.append(vertex)
|
||||
return children
|
||||
|
||||
def __repr__(self):
|
||||
node_ids = [node.id for node in self.nodes]
|
||||
edges_repr = "\n".join([f"{edge.source.id} --> {edge.target.id}" for edge in self.edges])
|
||||
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"
|
||||
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
import ast
|
||||
import inspect
|
||||
import pickle
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.graph.utils import UnbuiltObject
|
||||
from langflow.graph.vertex.utils import is_basic_type
|
||||
from langflow.interface.initialize import loading
|
||||
from langflow.interface.listing import lazy_load_dict
|
||||
from langflow.utils.constants import DIRECT_TYPES
|
||||
from langflow.utils.util import sync_to_async
|
||||
from loguru import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.base import Graph
|
||||
|
||||
|
||||
class Vertex:
|
||||
def __init__(
|
||||
self,
|
||||
data: Dict,
|
||||
graph: "Graph",
|
||||
base_type: Optional[str] = None,
|
||||
is_task: bool = False,
|
||||
params: Optional[Dict] = None,
|
||||
) -> None:
|
||||
self.graph = graph
|
||||
self.id: str = data["id"]
|
||||
self._data = data
|
||||
self.edges: List["Edge"] = []
|
||||
self.base_type: Optional[str] = base_type
|
||||
self._parse_data()
|
||||
self._built_object = UnbuiltObject()
|
||||
|
|
@ -39,43 +38,28 @@ class Vertex:
|
|||
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
|
||||
self.parent_is_top_level = False
|
||||
|
||||
def reset_params(self):
|
||||
for edge in self.edges:
|
||||
if edge.source != self:
|
||||
target_param = edge.target_param
|
||||
if target_param in ["document", "texts"]:
|
||||
# this means they got data and have already ingested it
|
||||
# so we continue after removing the param
|
||||
self.params.pop(target_param, None)
|
||||
continue
|
||||
|
||||
if target_param in self.params and not is_basic_type(self.params[target_param]):
|
||||
# edge.source.params = {}
|
||||
edge.source._build_params()
|
||||
edge.source._built_object = UnbuiltObject()
|
||||
edge.source._built = False
|
||||
|
||||
self.params[target_param] = edge.source
|
||||
@property
|
||||
def edges(self) -> List["Edge"]:
|
||||
return self.graph.get_vertex_edges(self.id)
|
||||
|
||||
def __getstate__(self):
|
||||
state_dict = self.__dict__.copy()
|
||||
try:
|
||||
# try pickling the built object
|
||||
# if it fails, then we need to delete it
|
||||
# and build it again
|
||||
pickle.dumps(state_dict["_built_object"])
|
||||
except Exception:
|
||||
self.reset_params()
|
||||
del state_dict["_built_object"]
|
||||
del state_dict["_built"]
|
||||
return state_dict
|
||||
return {
|
||||
"_data": self._data,
|
||||
"params": {},
|
||||
"base_type": self.base_type,
|
||||
"is_task": self.is_task,
|
||||
"id": self.id,
|
||||
"_built_object": UnbuiltObject(),
|
||||
"_built": False,
|
||||
"parent_node_id": self.parent_node_id,
|
||||
"parent_is_top_level": self.parent_is_top_level,
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self._data = state["_data"]
|
||||
self.params = state["params"]
|
||||
self.base_type = state["base_type"]
|
||||
self.is_task = state["is_task"]
|
||||
self.edges = state["edges"]
|
||||
self.id = state["id"]
|
||||
self._parse_data()
|
||||
if "_built_object" in state:
|
||||
|
|
@ -144,6 +128,10 @@ class Vertex:
|
|||
# and use that as the value for the param
|
||||
# If the type is "str", then we need to get the value of the "value" key
|
||||
# and use that as the value for the param
|
||||
|
||||
if self.graph is None:
|
||||
raise ValueError("Graph not found")
|
||||
|
||||
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||
params = self.params.copy() if self.params else {}
|
||||
|
||||
|
|
@ -155,9 +143,9 @@ class Vertex:
|
|||
if template_dict[param_key]["list"]:
|
||||
if param_key not in params:
|
||||
params[param_key] = []
|
||||
params[param_key].append(edge.source)
|
||||
elif edge.target.id == self.id:
|
||||
params[param_key] = edge.source
|
||||
params[param_key].append(self.graph.get_vertex(edge.source_id))
|
||||
elif edge.target_id == self.id:
|
||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||
|
||||
for key, value in template_dict.items():
|
||||
if key in params:
|
||||
|
|
@ -177,33 +165,33 @@ class Vertex:
|
|||
else:
|
||||
raise ValueError(f"File path not found for {self.vertex_type}")
|
||||
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
|
||||
val = value.get("value")
|
||||
if value.get("type") == "code":
|
||||
try:
|
||||
params[key] = ast.literal_eval(value.get("value"))
|
||||
params[key] = ast.literal_eval(val) if val else None
|
||||
except Exception as exc:
|
||||
logger.debug(f"Error parsing code: {exc}")
|
||||
params[key] = value.get("value")
|
||||
params[key] = val
|
||||
elif value.get("type") in ["dict", "NestedDict"]:
|
||||
# When dict comes from the frontend it comes as a
|
||||
# list of dicts, so we need to convert it to a dict
|
||||
# before passing it to the build method
|
||||
_value = value.get("value")
|
||||
if isinstance(_value, list):
|
||||
if isinstance(val, list):
|
||||
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||
elif isinstance(_value, dict):
|
||||
params[key] = _value
|
||||
elif value.get("type") == "int" and value.get("value") is not None:
|
||||
elif isinstance(val, dict):
|
||||
params[key] = val
|
||||
elif value.get("type") == "int" and val is not None:
|
||||
try:
|
||||
params[key] = int(value.get("value"))
|
||||
params[key] = int(val)
|
||||
except ValueError:
|
||||
params[key] = value.get("value")
|
||||
elif value.get("type") == "float" and value.get("value") is not None:
|
||||
params[key] = val
|
||||
elif value.get("type") == "float" and val is not None:
|
||||
try:
|
||||
params[key] = float(value.get("value"))
|
||||
params[key] = float(val)
|
||||
except ValueError:
|
||||
params[key] = value.get("value")
|
||||
params[key] = val
|
||||
else:
|
||||
params[key] = value.get("value")
|
||||
params[key] = val
|
||||
|
||||
if not value.get("required") and params.get(key) is None:
|
||||
if value.get("default"):
|
||||
|
|
@ -266,7 +254,7 @@ class Vertex:
|
|||
pass
|
||||
|
||||
# If there's no task_id, build the vertex locally
|
||||
await self.build(user_id)
|
||||
await self.build(user_id=user_id)
|
||||
return self._built_object
|
||||
|
||||
async def _build_node_and_update_params(self, key, node, user_id=None):
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import ast
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from langflow.graph.utils import flatten_list
|
||||
from langflow.graph.utils import UnbuiltObject, flatten_list
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
|
||||
|
||||
class AgentVertex(Vertex):
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="agents", params=params)
|
||||
def __init__(self, data: Dict, graph, params: Optional[Dict] = None):
|
||||
super().__init__(data, graph=graph, base_type="agents", params=params)
|
||||
|
||||
self.tools: List[Union[ToolkitVertex, ToolVertex]] = []
|
||||
self.chains: List[ChainVertex] = []
|
||||
|
|
@ -28,7 +28,7 @@ class AgentVertex(Vertex):
|
|||
for edge in self.edges:
|
||||
if not hasattr(edge, "source"):
|
||||
continue
|
||||
source_node = edge.source
|
||||
source_node = self.graph.get_vertex(edge.source_id)
|
||||
if isinstance(source_node, (ToolVertex, ToolkitVertex)):
|
||||
self.tools.append(source_node)
|
||||
elif isinstance(source_node, ChainVertex):
|
||||
|
|
@ -51,16 +51,21 @@ class AgentVertex(Vertex):
|
|||
|
||||
|
||||
class ToolVertex(Vertex):
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="tools", params=params)
|
||||
def __init__(
|
||||
self,
|
||||
data: Dict,
|
||||
graph,
|
||||
params: Optional[Dict] = None,
|
||||
):
|
||||
super().__init__(data, graph=graph, base_type="tools", params=params)
|
||||
|
||||
|
||||
class LLMVertex(Vertex):
|
||||
built_node_type = None
|
||||
class_built_object = None
|
||||
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="llms", params=params)
|
||||
def __init__(self, data: Dict, graph, params: Optional[Dict] = None):
|
||||
super().__init__(data, graph=graph, base_type="llms", params=params)
|
||||
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
# LLM is different because some models might take up too much memory
|
||||
|
|
@ -77,18 +82,18 @@ class LLMVertex(Vertex):
|
|||
|
||||
|
||||
class ToolkitVertex(Vertex):
|
||||
def __init__(self, data: Dict, params=None):
|
||||
super().__init__(data, base_type="toolkits", params=params)
|
||||
def __init__(self, data: Dict, graph, params=None):
|
||||
super().__init__(data, graph=graph, base_type="toolkits", params=params)
|
||||
|
||||
|
||||
class FileToolVertex(ToolVertex):
|
||||
def __init__(self, data: Dict, params=None):
|
||||
super().__init__(data, params=params)
|
||||
def __init__(self, data: Dict, graph, params=None):
|
||||
super().__init__(data, graph=graph, params=params)
|
||||
|
||||
|
||||
class WrapperVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="wrappers")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="wrappers")
|
||||
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
if not self._built or force:
|
||||
|
|
@ -99,14 +104,14 @@ class WrapperVertex(Vertex):
|
|||
|
||||
|
||||
class DocumentLoaderVertex(Vertex):
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="documentloaders", params=params)
|
||||
def __init__(self, data: Dict, graph, params: Optional[Dict] = None):
|
||||
super().__init__(data, graph=graph, base_type="documentloaders", params=params)
|
||||
|
||||
def _built_object_repr(self):
|
||||
# This built_object is a list of documents. Maybe we should
|
||||
# show how many documents are in the list?
|
||||
|
||||
if self._built_object:
|
||||
if self._built_object and not isinstance(self._built_object, UnbuiltObject):
|
||||
avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
|
||||
self._built_object
|
||||
)
|
||||
|
|
@ -117,28 +122,19 @@ class DocumentLoaderVertex(Vertex):
|
|||
|
||||
|
||||
class EmbeddingVertex(Vertex):
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="embeddings", params=params)
|
||||
def __init__(self, data: Dict, graph, params: Optional[Dict] = None):
|
||||
super().__init__(data, graph=graph, base_type="embeddings", params=params)
|
||||
|
||||
|
||||
class VectorStoreVertex(Vertex):
|
||||
def __init__(self, data: Dict, params=None):
|
||||
super().__init__(data, base_type="vectorstores")
|
||||
def __init__(self, data: Dict, graph, params=None):
|
||||
super().__init__(data, graph=graph, base_type="vectorstores")
|
||||
|
||||
self.params = params or {}
|
||||
|
||||
# VectorStores may contain databse connections
|
||||
# so we need to define the __reduce__ method and the __setstate__ method
|
||||
# to avoid pickling errors
|
||||
def clean_edges_for_pickling(self):
|
||||
# for each edge that has self as source
|
||||
# we need to clear the _built_object of the target
|
||||
# so that we don't try to pickle a database connection
|
||||
for edge in self.edges:
|
||||
if edge.source == self:
|
||||
edge.target._built_object = None
|
||||
edge.target._built = False
|
||||
edge.target.params[edge.target_param] = self
|
||||
|
||||
def remove_docs_and_texts_from_params(self):
|
||||
# remove documents and texts from params
|
||||
|
|
@ -146,17 +142,16 @@ class VectorStoreVertex(Vertex):
|
|||
self.params.pop("documents", None)
|
||||
self.params.pop("texts", None)
|
||||
|
||||
def __getstate__(self):
|
||||
# We want to save the params attribute
|
||||
# and if "documents" or "texts" are in the params
|
||||
# we want to remove them because they have already
|
||||
# been processed.
|
||||
params = self.params.copy()
|
||||
params.pop("documents", None)
|
||||
params.pop("texts", None)
|
||||
self.clean_edges_for_pickling()
|
||||
# def __getstate__(self):
|
||||
# # We want to save the params attribute
|
||||
# # and if "documents" or "texts" are in the params
|
||||
# # we want to remove them because they have already
|
||||
# # been processed.
|
||||
# params = self.params.copy()
|
||||
# params.pop("documents", None)
|
||||
# params.pop("texts", None)
|
||||
|
||||
return super().__getstate__()
|
||||
# return super().__getstate__()
|
||||
|
||||
def __setstate__(self, state):
|
||||
super().__setstate__(state)
|
||||
|
|
@ -164,24 +159,24 @@ class VectorStoreVertex(Vertex):
|
|||
|
||||
|
||||
class MemoryVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="memory")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="memory")
|
||||
|
||||
|
||||
class RetrieverVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="retrievers")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="retrievers")
|
||||
|
||||
|
||||
class TextSplitterVertex(Vertex):
|
||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="textsplitters", params=params)
|
||||
def __init__(self, data: Dict, graph, params: Optional[Dict] = None):
|
||||
super().__init__(data, graph=graph, base_type="textsplitters", params=params)
|
||||
|
||||
def _built_object_repr(self):
|
||||
# This built_object is a list of documents. Maybe we should
|
||||
# show how many documents are in the list?
|
||||
|
||||
if self._built_object:
|
||||
if self._built_object and not isinstance(self._built_object, UnbuiltObject):
|
||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||
\nAvg. Document Length (characters): {int(avg_length)}
|
||||
|
|
@ -190,8 +185,8 @@ class TextSplitterVertex(Vertex):
|
|||
|
||||
|
||||
class ChainVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="chains")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="chains")
|
||||
|
||||
async def build(
|
||||
self,
|
||||
|
|
@ -220,8 +215,8 @@ class ChainVertex(Vertex):
|
|||
|
||||
|
||||
class PromptVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="prompts")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="prompts")
|
||||
|
||||
async def build(
|
||||
self,
|
||||
|
|
@ -271,9 +266,13 @@ class PromptVertex(Vertex):
|
|||
# so the prompt format doesn't break
|
||||
artifacts.pop("handle_keys", None)
|
||||
try:
|
||||
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
||||
if (
|
||||
not hasattr(self._built_object, "template")
|
||||
and hasattr(self._built_object, "prompt")
|
||||
and not isinstance(self._built_object, UnbuiltObject)
|
||||
):
|
||||
template = self._built_object.prompt.template
|
||||
else:
|
||||
elif not isinstance(self._built_object, UnbuiltObject) and hasattr(self._built_object, "template"):
|
||||
template = self._built_object.template
|
||||
for key, value in artifacts.items():
|
||||
if value:
|
||||
|
|
@ -285,13 +284,13 @@ class PromptVertex(Vertex):
|
|||
|
||||
|
||||
class OutputParserVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="output_parsers")
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="output_parsers")
|
||||
|
||||
|
||||
class CustomComponentVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="custom_components", is_task=True)
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
|
||||
|
||||
def _built_object_repr(self):
|
||||
if self.task_id and self.is_task:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import ast
|
||||
import operator
|
||||
import warnings
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
from cachetools import TTLCache, cachedmethod
|
||||
from fastapi import HTTPException
|
||||
|
||||
from langflow.interface.custom.code_parser import CodeParser
|
||||
from langflow.utils import validate
|
||||
|
||||
|
|
@ -23,11 +25,20 @@ class Component:
|
|||
code: Optional[str] = None
|
||||
_function_entrypoint_name: str = "build"
|
||||
field_config: dict = {}
|
||||
_user_id: Optional[str]
|
||||
|
||||
def __init__(self, **data):
|
||||
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
if key == "user_id":
|
||||
setattr(self, "_user_id", value)
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key == "_user_id" and hasattr(self, "_user_id"):
|
||||
warnings.warn("user_id is immutable and cannot be changed.")
|
||||
super().__setattr__(key, value)
|
||||
|
||||
@cachedmethod(cache=operator.attrgetter("cache"))
|
||||
def get_code_tree(self, code: str):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from uuid import UUID
|
|||
import yaml
|
||||
from cachetools import TTLCache, cachedmethod
|
||||
from fastapi import HTTPException
|
||||
|
||||
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||
from langflow.interface.custom.component import Component
|
||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||
|
|
@ -15,7 +14,7 @@ from langflow.interface.custom.utils import (
|
|||
)
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.database.utils import session_getter
|
||||
from langflow.services.deps import get_db_service
|
||||
from langflow.services.deps import get_credential_service, get_db_service
|
||||
from langflow.utils import validate
|
||||
|
||||
|
||||
|
|
@ -184,6 +183,37 @@ class CustomComponent(Component):
|
|||
|
||||
return super().build_template_config(attributes)
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
def get_credential(name: str):
|
||||
if hasattr(self, "_user_id") and not self._user_id:
|
||||
raise ValueError(f"User id is not set for {self.__class__.__name__}")
|
||||
credential_service = get_credential_service() # Get service instance
|
||||
# Retrieve and decrypt the credential by name for the current user
|
||||
db_service = get_db_service()
|
||||
with session_getter(db_service) as session:
|
||||
return credential_service.get_credential(user_id=self._user_id, name=name, session=session)
|
||||
|
||||
return get_credential
|
||||
|
||||
def list_key_names(self):
|
||||
if hasattr(self, "_user_id") and not self._user_id:
|
||||
raise ValueError(f"User id is not set for {self.__class__.__name__}")
|
||||
credential_service = get_credential_service()
|
||||
db_service = get_db_service()
|
||||
with session_getter(db_service) as session:
|
||||
return credential_service.list_credentials(user_id=self._user_id, session=session)
|
||||
|
||||
def index(self, value: int = 0):
|
||||
"""Returns a function that returns the value at the given index in the iterable."""
|
||||
|
||||
def get_index(iterable: List[Any]):
|
||||
if iterable:
|
||||
return iterable[value]
|
||||
return iterable
|
||||
|
||||
return get_index
|
||||
|
||||
@property
|
||||
def get_function(self):
|
||||
return validate.create_function(self.code, self.function_entrypoint_name)
|
||||
|
|
@ -201,7 +231,7 @@ class CustomComponent(Component):
|
|||
return await build_sorted_vertices(graph_data, self.user_id)
|
||||
|
||||
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
||||
if not self.user_id:
|
||||
if not self._user_id:
|
||||
raise ValueError("Session is invalid")
|
||||
try:
|
||||
get_session = get_session or session_getter
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ import contextlib
|
|||
import re
|
||||
import traceback
|
||||
import warnings
|
||||
from typing import Any, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from cachetools import LRUCache, cached
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.utils import get_new_key
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
|
|
@ -35,6 +33,7 @@ from langflow.template.field.base import TemplateField
|
|||
from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE
|
||||
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
|
||||
from langflow.utils.util import get_base_classes
|
||||
from loguru import logger
|
||||
|
||||
|
||||
# Used to get the base_classes list
|
||||
|
|
@ -201,7 +200,9 @@ def update_attributes(frontend_node, template_config):
|
|||
frontend_node[attribute] = template_config[attribute]
|
||||
|
||||
|
||||
def build_field_config(custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None):
|
||||
def build_field_config(
|
||||
custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None, update_field=None
|
||||
):
|
||||
"""Build the field configuration for a custom component"""
|
||||
|
||||
try:
|
||||
|
|
@ -222,7 +223,19 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
|
|||
) from exc
|
||||
|
||||
try:
|
||||
return custom_class(user_id=user_id).build_config()
|
||||
build_config: Dict = custom_class(user_id=user_id).build_config()
|
||||
|
||||
for field_name, field_dict in build_config.items():
|
||||
if update_field is not None and field_name != update_field:
|
||||
continue
|
||||
try:
|
||||
update_field_dict(field_dict)
|
||||
build_config[field_name] = field_dict
|
||||
except Exception as exc:
|
||||
logger.error(f"Error while getting build_config: {str(exc)}")
|
||||
|
||||
return build_config
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(f"Error while building field config: {str(exc)}")
|
||||
raise HTTPException(
|
||||
|
|
@ -234,6 +247,18 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
|
|||
) from exc
|
||||
|
||||
|
||||
def update_field_dict(field_dict):
|
||||
"""Update the field dictionary by calling options() or value() if they are callable"""
|
||||
if "options" in field_dict and callable(field_dict["options"]):
|
||||
field_dict["options"] = field_dict["options"]()
|
||||
# Also update the "refresh" key
|
||||
field_dict["refresh"] = True
|
||||
|
||||
if "value" in field_dict and callable(field_dict["value"]):
|
||||
field_dict["value"] = field_dict["value"](field_dict.get("options", []))
|
||||
field_dict["refresh"] = True
|
||||
|
||||
|
||||
def add_extra_fields(frontend_node, field_config, function_args):
|
||||
"""Add extra fields to the frontend node"""
|
||||
if not function_args:
|
||||
|
|
@ -314,7 +339,9 @@ def add_output_types(frontend_node, return_types: List[str]):
|
|||
|
||||
|
||||
def build_langchain_template_custom_component(
|
||||
custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None
|
||||
custom_component: CustomComponent,
|
||||
user_id: Optional[Union[str, UUID]] = None,
|
||||
update_field: Optional[str] = None,
|
||||
):
|
||||
"""Build a custom component template for the langchain"""
|
||||
try:
|
||||
|
|
@ -328,7 +355,7 @@ def build_langchain_template_custom_component(
|
|||
|
||||
update_attributes(frontend_node, template_config)
|
||||
logger.debug("Updated attributes")
|
||||
field_config = build_field_config(custom_component, user_id=user_id)
|
||||
field_config = build_field_config(custom_component, user_id=user_id, update_field=update_field)
|
||||
logger.debug("Built field config")
|
||||
entrypoint_args = custom_component.get_function_entrypoint_args
|
||||
|
||||
|
|
@ -514,3 +541,9 @@ def merge_nested_dicts(dict1, dict2):
|
|||
else:
|
||||
dict1[key] = value
|
||||
return dict1
|
||||
|
||||
|
||||
def create_and_validate_component(code: str) -> CustomComponent:
|
||||
component = CustomComponent(code=code)
|
||||
component.validate()
|
||||
return component
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer
|
|||
from jose import JWTError, jwt
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.services.database.models.api_key.api_key import ApiKey
|
||||
from langflow.services.database.models.api_key.model import ApiKey
|
||||
from langflow.services.database.models.api_key.crud import check_key
|
||||
from langflow.services.database.models.user.crud import get_user_by_id, get_user_by_username, update_user_last_login_at
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_session, get_settings_service
|
||||
|
||||
oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False)
|
||||
|
|
|
|||
0
src/backend/langflow/services/credentials/__init__.py
Normal file
0
src/backend/langflow/services/credentials/__init__.py
Normal file
15
src/backend/langflow/services/credentials/factory.py
Normal file
15
src/backend/langflow/services/credentials/factory.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from langflow.services.credentials.service import CredentialService
|
||||
from langflow.services.factory import ServiceFactory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.settings.service import SettingsService
|
||||
|
||||
|
||||
class CredentialServiceFactory(ServiceFactory):
|
||||
def __init__(self):
|
||||
super().__init__(CredentialService)
|
||||
|
||||
def create(self, settings_service: "SettingsService"):
|
||||
return CredentialService(settings_service)
|
||||
32
src/backend/langflow/services/credentials/service.py
Normal file
32
src/backend/langflow/services/credentials/service.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from typing import TYPE_CHECKING, Union
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Depends
|
||||
from langflow.services.auth import utils as auth_utils
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.database.models.credential.model import Credential
|
||||
from langflow.services.deps import get_session
|
||||
from sqlmodel import Session
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.settings.service import SettingsService
|
||||
|
||||
|
||||
class CredentialService(Service):
|
||||
name = "credential_service"
|
||||
|
||||
def __init__(self, settings_service: "SettingsService"):
|
||||
self.settings_service = settings_service
|
||||
|
||||
def get_credential(self, user_id: Union[UUID, str], name: str, session: Session = Depends(get_session)) -> str:
|
||||
# we get the credential from the database
|
||||
credential = session.query(Credential).filter(Credential.user_id == user_id, Credential.name == name).first()
|
||||
# we decrypt the value
|
||||
if not credential:
|
||||
raise ValueError(f"{name} credential not found.")
|
||||
decrypted = auth_utils.decrypt_api_key(credential.value, settings_service=self.settings_service)
|
||||
return decrypted
|
||||
|
||||
def list_credentials(self, user_id: Union[UUID, str], session: Session = Depends(get_session)) -> list[Credential]:
|
||||
credentials = session.query(Credential).filter(Credential.user_id == user_id).all()
|
||||
return [credential.name for credential in credentials]
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from .api_key import ApiKey
|
||||
from .credential import Credential
|
||||
from .flow import Flow
|
||||
from .user import User
|
||||
from .api_key import ApiKey
|
||||
|
||||
__all__ = ["Flow", "User", "ApiKey"]
|
||||
__all__ = ["Flow", "User", "ApiKey", "Credential"]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
from .api_key import ApiKey, ApiKeyCreate, UnmaskedApiKeyRead, ApiKeyRead
|
||||
from .model import ApiKey, ApiKeyCreate, UnmaskedApiKeyRead, ApiKeyRead
|
||||
|
||||
__all__ = ["ApiKey", "ApiKeyCreate", "UnmaskedApiKeyRead", "ApiKeyRead"]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
from pydantic import validator
|
||||
from sqlmodel import Field, Relationship
|
||||
from uuid import UUID, uuid4
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from datetime import datetime
|
||||
from langflow.services.database.models.base import SQLModelSerializable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import validator
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.user import User
|
||||
|
||||
|
||||
class ApiKeyBase(SQLModelSerializable):
|
||||
class ApiKeyBase(SQLModel):
|
||||
name: Optional[str] = Field(index=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
last_used_at: Optional[datetime] = Field(default=None, nullable=True)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
from sqlmodel import SQLModel
|
||||
import orjson
|
||||
|
||||
|
||||
|
|
@ -16,9 +15,3 @@ def orjson_dumps(v, *, default=None, sort_keys=False, indent_2=True):
|
|||
if default is None:
|
||||
return orjson.dumps(v, option=option).decode()
|
||||
return orjson.dumps(v, default=default, option=option).decode()
|
||||
|
||||
|
||||
class SQLModelSerializable(SQLModel):
|
||||
# TODO[pydantic]: The following keys were removed: `json_loads`, `json_dumps`.
|
||||
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
from .component import Component, ComponentModel
|
||||
from .model import Component, ComponentModel
|
||||
|
||||
__all__ = ["Component", "ComponentModel"]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||
from sqlmodel import Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class Component(SQLModelSerializable, table=True):
|
||||
class Component(SQLModel, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
frontend_node_id: uuid.UUID = Field(index=True)
|
||||
name: str = Field(index=True)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from .model import Credential, CredentialCreate, CredentialRead, CredentialUpdate
|
||||
|
||||
__all__ = ["Credential", "CredentialCreate", "CredentialRead", "CredentialUpdate"]
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from langflow.services.database.models.credential.schema import CredentialType
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.user import User
|
||||
|
||||
|
||||
class CredentialBase(SQLModel):
|
||||
name: Optional[str] = Field(None, description="Name of the credential")
|
||||
value: Optional[str] = Field(None, description="Encrypted value of the credential")
|
||||
provider: Optional[str] = Field(None, description="Provider of the credential (e.g OpenAI)")
|
||||
|
||||
|
||||
class Credential(CredentialBase, table=True):
|
||||
id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True, description="Unique ID for the credential")
|
||||
# name is unique per user
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time of the credential")
|
||||
updated_at: Optional[datetime] = Field(None, description="Last update time of the credential")
|
||||
# foreign key to user table
|
||||
user_id: UUID = Field(description="User ID associated with this credential", foreign_key="user.id")
|
||||
user: "User" = Relationship(back_populates="credentials")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
user: "User" = Relationship(back_populates="credentials")
|
||||
|
||||
|
||||
class CredentialCreate(CredentialBase):
|
||||
# AcceptedProviders is a custom Enum
|
||||
provider: CredentialType = Field(description="Provider of the credential (e.g OpenAI)")
|
||||
|
||||
|
||||
class CredentialRead(SQLModel):
|
||||
id: UUID
|
||||
name: Optional[str] = Field(None, description="Name of the credential")
|
||||
provider: Optional[str] = Field(None, description="Provider of the credential (e.g OpenAI)")
|
||||
|
||||
|
||||
class CredentialUpdate(SQLModel):
|
||||
id: UUID # Include the ID for updating
|
||||
name: Optional[str] = Field(None, description="Name of the credential")
|
||||
value: Optional[str] = Field(None, description="Encrypted value of the credential")
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class CredentialType(str, Enum):
|
||||
"""CredentialType is an Enum of the accepted providers"""
|
||||
|
||||
OPENAI_API_KEY = "OPENAI_API_KEY"
|
||||
ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY"
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
from .flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||
from .model import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||
|
||||
__all__ = ["Flow", "FlowCreate", "FlowRead", "FlowUpdate"]
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import field_serializer, field_validator
|
||||
from sqlmodel import JSON, Column, Field, Relationship
|
||||
|
||||
from langflow.services.database.models.base import SQLModelSerializable
|
||||
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.user import User
|
||||
|
||||
|
||||
class FlowBase(SQLModelSerializable):
|
||||
class FlowBase(SQLModel):
|
||||
name: str = Field(index=True)
|
||||
description: Optional[str] = Field(index=True, nullable=True, default=None)
|
||||
data: Optional[Dict] = Field(default=None, nullable=True)
|
||||
|
|
@ -69,7 +67,7 @@ class FlowRead(FlowBase):
|
|||
user_id: UUID = Field()
|
||||
|
||||
|
||||
class FlowUpdate(SQLModelSerializable):
|
||||
class FlowUpdate(SQLModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
data: Optional[Dict] = None
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from .user import User, UserCreate, UserRead, UserUpdate
|
||||
from .model import User, UserCreate, UserRead, UserUpdate
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from datetime import datetime, timezone
|
|||
from typing import Union
|
||||
from uuid import UUID
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from langflow.services.database.models.user.user import User, UserUpdate
|
||||
from langflow.services.database.models.user.model import User, UserUpdate
|
||||
from langflow.services.deps import get_session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
from langflow.services.database.models.base import SQLModel, SQLModelSerializable
|
||||
from sqlmodel import Field, Relationship
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.api_key import ApiKey
|
||||
from langflow.services.database.models.credential import Credential
|
||||
from langflow.services.database.models.flow import Flow
|
||||
|
||||
|
||||
class User(SQLModelSerializable, table=True):
|
||||
class User(SQLModel, table=True):
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||
username: str = Field(index=True, unique=True)
|
||||
password: str = Field()
|
||||
|
|
@ -27,6 +26,10 @@ class User(SQLModelSerializable, table=True):
|
|||
)
|
||||
store_api_key: str = Field(default=None, nullable=True)
|
||||
flows: list["Flow"] = Relationship(back_populates="user")
|
||||
credentials: list["Credential"] = Relationship(
|
||||
back_populates="user",
|
||||
sa_relationship_kwargs={"cascade": "delete"},
|
||||
)
|
||||
|
||||
|
||||
class UserCreate(SQLModel):
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import command, util
|
||||
from alembic.config import Config
|
||||
from loguru import logger
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlmodel import Session, SQLModel, create_engine
|
||||
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.database import models # noqa
|
||||
from langflow.services.database.models.user.crud import get_user_by_username
|
||||
from langflow.services.database.utils import Result, TableResults
|
||||
from langflow.services.deps import get_settings_service
|
||||
from langflow.services.utils import teardown_superuser
|
||||
from sqlalchemy import inspect
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlmodel import SQLModel, Session, create_engine
|
||||
from loguru import logger
|
||||
from alembic.config import Config
|
||||
from alembic import command, util
|
||||
from langflow.services.database import models # noqa
|
||||
import time
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.engine import Engine
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ if TYPE_CHECKING:
|
|||
from langflow.services.settings.service import SettingsService
|
||||
from langflow.services.store.service import StoreService
|
||||
from langflow.services.task.service import TaskService
|
||||
from langflow.services.credentials.service import CredentialService
|
||||
|
||||
|
||||
def get_credential_service() -> "CredentialService":
|
||||
return service_manager.get(ServiceType.CREDENTIAL_SERVICE)
|
||||
|
||||
|
||||
def get_settings_service() -> "SettingsService":
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@ class ServiceType(str, Enum):
|
|||
SESSION_SERVICE = "session_service"
|
||||
TASK_SERVICE = "task_service"
|
||||
STORE_SERVICE = "store_service"
|
||||
CREDENTIAL_SERVICE = "credential_service"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ForbiddenError(CustomException):
|
|||
|
||||
class APIKeyError(CustomException):
|
||||
def __init__(self, detail="API key error"):
|
||||
super().__init__(detail, 401)
|
||||
super().__init__(detail, 400) #! Should be 401
|
||||
|
||||
|
||||
class FilterError(CustomException):
|
||||
|
|
|
|||
|
|
@ -445,6 +445,8 @@ class StoreService(Service):
|
|||
|
||||
result: List[ListComponentResponse] = []
|
||||
authorized = False
|
||||
metadata = {}
|
||||
comp_count = 0
|
||||
try:
|
||||
result, metadata = await self.query_components(
|
||||
api_key=store_api_key,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ def get_factories_and_deps():
|
|||
from langflow.services.settings import factory as settings_factory
|
||||
from langflow.services.store import factory as store_factory
|
||||
from langflow.services.task import factory as task_factory
|
||||
from langflow.services.credentials import factory as credentials_factory
|
||||
|
||||
return [
|
||||
(settings_factory.SettingsServiceFactory(), []),
|
||||
|
|
@ -40,11 +41,12 @@ def get_factories_and_deps():
|
|||
[ServiceType.CACHE_SERVICE],
|
||||
),
|
||||
(store_factory.StoreServiceFactory(), [ServiceType.SETTINGS_SERVICE]),
|
||||
(credentials_factory.CredentialServiceFactory(), [ServiceType.SETTINGS_SERVICE]),
|
||||
]
|
||||
|
||||
|
||||
def get_or_create_super_user(session: Session, username, password, is_default):
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
|
||||
user = session.query(User).filter(User.username == username).first()
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ def teardown_superuser(settings_service, session):
|
|||
try:
|
||||
logger.debug("AUTO_LOGIN is set to False. Removing default superuser if exists.")
|
||||
username = DEFAULT_SUPERUSER
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.model import User
|
||||
|
||||
user = session.query(User).filter(User.username == username).first()
|
||||
if user and user.is_superuser:
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ class TemplateFieldCreator(BaseModel, ABC):
|
|||
info: Optional[str] = ""
|
||||
"""Additional information about the field to be shown in the tooltip. Defaults to an empty string."""
|
||||
|
||||
refresh: Optional[bool] = None
|
||||
"""Specifies if the field should be refreshed. Defaults to False."""
|
||||
|
||||
def to_dict(self):
|
||||
result = self.model_dump()
|
||||
# Remove key if it is None
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
from collections import defaultdict
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import ClassVar, DefaultDict, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langflow.template.frontend_node.formatter import field_formatters
|
||||
from langflow.template.field.base import TemplateField
|
||||
from langflow.template.frontend_node.constants import (
|
||||
CLASSES_TO_REMOVE,
|
||||
FORCE_SHOW_FIELDS,
|
||||
)
|
||||
from langflow.template.field.base import TemplateField
|
||||
from langflow.template.frontend_node.formatter import field_formatters
|
||||
from langflow.template.template.base import Template
|
||||
from langflow.utils import constants
|
||||
|
||||
|
|
@ -51,7 +51,9 @@ class FrontendNode(BaseModel):
|
|||
documentation: str = ""
|
||||
custom_fields: Optional[DefaultDict[str, List[str]]] = defaultdict(list)
|
||||
output_types: List[str] = []
|
||||
full_path: Optional[str] = None
|
||||
field_formatters: FieldFormatters = Field(default_factory=FieldFormatters)
|
||||
|
||||
beta: bool = False
|
||||
error: Optional[str] = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Optional
|
||||
from loguru import logger
|
||||
from pathlib import Path
|
||||
from rich.logging import RichHandler
|
||||
from platformdirs import user_cache_dir
|
||||
import os
|
||||
import orjson
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import orjson
|
||||
from loguru import logger
|
||||
from platformdirs import user_cache_dir
|
||||
from rich.logging import RichHandler
|
||||
|
||||
VALID_LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
|
||||
|
|
@ -66,4 +66,4 @@ def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None):
|
|||
|
||||
logger.debug(f"Logger set up with log level: {log_level}")
|
||||
if log_file:
|
||||
logger.info(f"Log file: {log_file}")
|
||||
logger.debug(f"Log file: {log_file}")
|
||||
|
|
|
|||
|
|
@ -28,16 +28,16 @@ def extract_input_variables(nodes):
|
|||
return nodes
|
||||
|
||||
|
||||
def get_root_node(graph):
|
||||
def get_root_vertex(graph):
|
||||
"""
|
||||
Returns the root node of the template.
|
||||
"""
|
||||
incoming_edges = {edge.source for edge in graph.edges}
|
||||
incoming_edges = {edge.source_id for edge in graph.edges}
|
||||
|
||||
if not incoming_edges and len(graph.nodes) == 1:
|
||||
return graph.nodes[0]
|
||||
if not incoming_edges and len(graph.vertices) == 1:
|
||||
return graph.vertices[0]
|
||||
|
||||
return next((node for node in graph.nodes if node not in incoming_edges), None)
|
||||
return next((node for node in graph.vertices if node.id not in incoming_edges), None)
|
||||
|
||||
|
||||
def build_json(root, graph) -> Dict:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import ast
|
||||
import contextlib
|
||||
import importlib
|
||||
import types
|
||||
from types import FunctionType
|
||||
from typing import Dict
|
||||
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ def eval_function(function_string: str):
|
|||
(
|
||||
obj
|
||||
for name, obj in namespace.items()
|
||||
if isinstance(obj, types.FunctionType) and obj.__code__.co_filename == "<string>"
|
||||
if isinstance(obj, FunctionType) and obj.__code__.co_filename == "<string>"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,11 +21,17 @@ import PromptAreaComponent from "../../../../components/promptComponent";
|
|||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { TOOLTIP_EMPTY } from "../../../../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
TOOLTIP_EMPTY,
|
||||
} from "../../../../constants/constants";
|
||||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
||||
import { postCustomComponentUpdate } from "../../../../controllers/API";
|
||||
import { ParameterComponentType } from "../../../../types/components";
|
||||
import { NodeDataType } from "../../../../types/flow";
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
|
|
@ -59,6 +65,7 @@ export default function ParameterComponent({
|
|||
const ref = useRef<HTMLDivElement>(null);
|
||||
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [position, setPosition] = useState(0);
|
||||
const { setTabsState, tabId, flows } = useContext(FlowsContext);
|
||||
|
|
@ -93,6 +100,25 @@ export default function ParameterComponent({
|
|||
|
||||
const { takeSnapshot } = useContext(undoRedoContext);
|
||||
|
||||
const handleUpdateValues = async (name: string, data: NodeDataType) => {
|
||||
const code = data.node?.template["code"]?.value;
|
||||
if (!code) {
|
||||
console.error("Code not found in the template");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await postCustomComponentUpdate(code, name);
|
||||
if (res.status === 200 && data.node?.template) {
|
||||
let clone = cloneDeep(data);
|
||||
clone.node!.template[name] = res.data.template[name];
|
||||
setData(clone);
|
||||
}
|
||||
} catch (err) {
|
||||
setErrorData(err as { title: string; list?: Array<string> });
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnNewValue = (
|
||||
newValue: string | string[] | boolean | Object[]
|
||||
): void => {
|
||||
|
|
@ -222,17 +248,7 @@ export default function ParameterComponent({
|
|||
}, [tooltipTitle, flow]);
|
||||
|
||||
return !showNode ? (
|
||||
left &&
|
||||
(type === "str" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict") &&
|
||||
!optionalHandle ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
|
|
@ -305,17 +321,7 @@ export default function ParameterComponent({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{left &&
|
||||
(type === "str" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict") &&
|
||||
!optionalHandle ? (
|
||||
{left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
|
|
@ -409,12 +415,26 @@ export default function ParameterComponent({
|
|||
) : left === true &&
|
||||
type === "str" &&
|
||||
data.node?.template[name].options ? (
|
||||
<div className="mt-2 w-full">
|
||||
<Dropdown
|
||||
options={data.node.template[name].options}
|
||||
onSelect={handleOnNewValue}
|
||||
value={data.node.template[name].value ?? "Choose an option"}
|
||||
></Dropdown>
|
||||
// TODO: Improve CSS
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div className="w-5/6 flex-grow">
|
||||
<Dropdown
|
||||
options={data.node.template[name].options}
|
||||
onSelect={handleOnNewValue}
|
||||
value={data.node.template[name].value ?? "Choose an option"}
|
||||
id={"dropdown-" + index}
|
||||
/>
|
||||
</div>
|
||||
{data.node?.template[name].refresh && (
|
||||
<button
|
||||
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
|
||||
onClick={() => {
|
||||
handleUpdateValues(name, data);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="RefreshCcw" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : left === true && type === "code" ? (
|
||||
<div className="mt-2 w-full">
|
||||
|
|
@ -491,6 +511,7 @@ export default function ParameterComponent({
|
|||
data.node!.template[name].value = newValue;
|
||||
handleOnNewValue(newValue);
|
||||
}}
|
||||
id="div-dict-input"
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "dict" ? (
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default function GenericNode({
|
|||
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
|
||||
useContext(typesContext);
|
||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||
const [inputName, setInputName] = useState(true);
|
||||
const [inputName, setInputName] = useState(false);
|
||||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
|
|
@ -206,13 +206,21 @@ export default function GenericNode({
|
|||
) : (
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div
|
||||
className="generic-node-tooltip-div text-primary"
|
||||
className="flex"
|
||||
onDoubleClick={() => {
|
||||
setInputName(true);
|
||||
takeSnapshot();
|
||||
}}
|
||||
>
|
||||
{data.node?.display_name}
|
||||
<div className="generic-node-tooltip-div pr-3 text-primary">
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
{data.node?.flow && (
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-5 w-5 text-primary-content"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const SanitizedHTMLWrapper = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
data-testid="edit-prompt-sanitized"
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
|
||||
suppressContentEditableWarning={suppressWarning}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export default function CodeAreaComponent({
|
|||
<div className="flex w-full items-center">
|
||||
<span
|
||||
id={id}
|
||||
data-testid={id}
|
||||
className={
|
||||
editNode
|
||||
? "input-edit-node input-dialog"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import {
|
|||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { codeTabsPropsType } from "../../types/components";
|
||||
|
|
@ -242,24 +243,10 @@ export default function CodeTabsComponent({
|
|||
templateField.charAt(0) !== "_" &&
|
||||
node.data.node.template[templateField]
|
||||
.show &&
|
||||
(node.data.node.template[templateField]
|
||||
.type === "str" ||
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
node.data.node.template[templateField]
|
||||
.type === "bool" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "float" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "code" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "prompt" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "file" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "int" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "dict" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "NestedDict")
|
||||
.type
|
||||
)
|
||||
)
|
||||
.map((templateField, indx) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default function DictComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
id = "",
|
||||
}: DictComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
@ -30,7 +31,7 @@ export default function DictComponent({
|
|||
)}
|
||||
>
|
||||
{
|
||||
<div className="flex w-full gap-3">
|
||||
<div className="flex w-full gap-3" data-testid={id}>
|
||||
<DictAreaModal
|
||||
value={ref.current}
|
||||
onChange={(obj) => {
|
||||
|
|
@ -45,6 +46,7 @@ export default function DictComponent({
|
|||
: "input-disable pointer-events-none cursor-pointer"
|
||||
}
|
||||
placeholder="Click to edit your dictionary..."
|
||||
data-testid="dict-input"
|
||||
/>
|
||||
</DictAreaModal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default function Dropdown({
|
|||
editNode = false,
|
||||
numberOfOptions = 0,
|
||||
apiModal = false,
|
||||
id = "",
|
||||
}: DropDownComponentType): JSX.Element {
|
||||
let [internalValue, setInternalValue] = useState(
|
||||
value === "" || !value ? "Choose an option" : value
|
||||
|
|
@ -31,15 +32,19 @@ export default function Dropdown({
|
|||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className={editNode ? "mt-1" : "relative mt-1"}>
|
||||
<div className={"relative mt-1"}>
|
||||
<Listbox.Button
|
||||
data-test={`${id ?? ""}`}
|
||||
className={
|
||||
editNode
|
||||
? "dropdown-component-outline"
|
||||
: "dropdown-component-false-outline"
|
||||
}
|
||||
>
|
||||
<span className="dropdown-component-display">
|
||||
<span
|
||||
className="dropdown-component-display"
|
||||
data-testid={`${id ?? ""}-display`}
|
||||
>
|
||||
{internalValue}
|
||||
</span>
|
||||
<span className={"dropdown-component-arrow"}>
|
||||
|
|
@ -63,7 +68,7 @@ export default function Dropdown({
|
|||
editNode
|
||||
? "dropdown-component-true-options nowheel custom-scroll"
|
||||
: "dropdown-component-false-options nowheel custom-scroll",
|
||||
apiModal ? "mb-2 w-[250px]" : "absolute"
|
||||
apiModal ? "mb-2 w-[250px]" : "absolute w-full"
|
||||
)}
|
||||
>
|
||||
{options.map((option, id) => (
|
||||
|
|
@ -86,6 +91,7 @@ export default function Dropdown({
|
|||
selected ? "font-semibold" : "font-normal",
|
||||
"block truncate "
|
||||
)}
|
||||
data-testid={`${option}-${id ?? ""}-option`}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import { IconComponentProps } from "../../types/components";
|
|||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
|
||||
const ForwardedIconComponent = forwardRef(
|
||||
({ name, className, iconColor, stroke }: IconComponentProps, ref) => {
|
||||
(
|
||||
{ name, className, iconColor, stroke, id = "" }: IconComponentProps,
|
||||
ref
|
||||
) => {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return (
|
||||
<TargetIcon
|
||||
|
|
@ -12,6 +15,7 @@ const ForwardedIconComponent = forwardRef(
|
|||
style={iconColor ? { color: iconColor } : {}}
|
||||
ref={ref}
|
||||
stroke={stroke ? stroke : "currentColor"}
|
||||
data-testid={id ? `${id}-${name}` : "icon-" + name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default function KeypairListComponent({
|
|||
return (
|
||||
<div key={idx} className="flex w-full gap-2">
|
||||
<Input
|
||||
id={"keypair" + index}
|
||||
id={editNode ? "editNodekeypair" + index : "keypair" + index}
|
||||
type="text"
|
||||
value={key.trim()}
|
||||
className={classNames(
|
||||
|
|
@ -71,7 +71,11 @@ export default function KeypairListComponent({
|
|||
/>
|
||||
|
||||
<Input
|
||||
id={"keypair" + (index + 100).toString()}
|
||||
id={
|
||||
editNode
|
||||
? "editNodekeypair" + (index + 100).toString()
|
||||
: "keypair" + (index + 100).toString()
|
||||
}
|
||||
type="text"
|
||||
value={obj[key]}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
|
|
@ -88,7 +92,11 @@ export default function KeypairListComponent({
|
|||
newInputList.push({ "": "" });
|
||||
onChange(newInputList);
|
||||
}}
|
||||
id={"plusbtn" + index.toString()}
|
||||
id={
|
||||
editNode
|
||||
? "editNodeplusbtn" + index.toString()
|
||||
: "plusbtn" + index.toString()
|
||||
}
|
||||
>
|
||||
<IconComponent
|
||||
name="Plus"
|
||||
|
|
@ -102,7 +110,11 @@ export default function KeypairListComponent({
|
|||
newInputList.splice(index, 1);
|
||||
onChange(newInputList);
|
||||
}}
|
||||
id={"minusbtn" + index.toString()}
|
||||
id={
|
||||
editNode
|
||||
? "editNodeminusbtn" + index.toString()
|
||||
: "minusbtn" + index.toString()
|
||||
}
|
||||
>
|
||||
<IconComponent
|
||||
name="X"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export default function PromptAreaComponent({
|
|||
<div className="flex w-full items-center">
|
||||
<span
|
||||
id={id}
|
||||
data-testid={id}
|
||||
className={
|
||||
editNode
|
||||
? "input-edit-node input-dialog"
|
||||
|
|
@ -64,6 +65,7 @@ export default function PromptAreaComponent({
|
|||
</span>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export default function TextAreaComponent({
|
|||
/>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
|
|
|
|||
|
|
@ -663,3 +663,15 @@ export const LAST_USED_SPAN_1 = "The last time this key was used.";
|
|||
|
||||
export const LAST_USED_SPAN_2 =
|
||||
"Accurate to within the hour from the most recent usage.";
|
||||
|
||||
export const LANGFLOW_SUPPORTED_TYPES = new Set([
|
||||
"str",
|
||||
"bool",
|
||||
"float",
|
||||
"code",
|
||||
"prompt",
|
||||
"file",
|
||||
"int",
|
||||
"dict",
|
||||
"NestedDict",
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ const FlowsContextInitialValue: FlowsContextType = {
|
|||
isLoading: true,
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: async (newProject: boolean, flowData?: FlowType) => "",
|
||||
addFlow: async (
|
||||
newProject: boolean,
|
||||
flowData?: FlowType,
|
||||
override?: boolean
|
||||
) => "",
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => uid(),
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
|
|
@ -78,7 +82,7 @@ const FlowsContextInitialValue: FlowsContextType = {
|
|||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) => {},
|
||||
saveComponent: async (component: NodeDataType) => "",
|
||||
saveComponent: async (component: NodeDataType, override: boolean) => "",
|
||||
deleteComponent: (key: string) => {},
|
||||
version: "",
|
||||
};
|
||||
|
|
@ -496,7 +500,8 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
const addFlow = async (
|
||||
newProject: Boolean,
|
||||
flow?: FlowType
|
||||
flow?: FlowType,
|
||||
override?: boolean
|
||||
): Promise<String | undefined> => {
|
||||
if (newProject) {
|
||||
let flowData = flow
|
||||
|
|
@ -505,6 +510,15 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
|
||||
if (override) {
|
||||
deleteComponent(flow!.name);
|
||||
const newFlow = createNewFlow(flowData, flow!);
|
||||
const { id } = await saveFlowToDatabase(newFlow);
|
||||
newFlow.id = id;
|
||||
addFlowToLocalState(newFlow);
|
||||
return;
|
||||
}
|
||||
|
||||
const newFlow = createNewFlow(flowData, flow!);
|
||||
|
||||
const newName = addVersionToDuplicates(newFlow, flows);
|
||||
|
|
@ -595,14 +609,16 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
}
|
||||
newFlow = {
|
||||
...newFlow,
|
||||
};
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
|
||||
async function saveFlow(newFlow: FlowType, silent?: boolean) {
|
||||
console.log(newFlow);
|
||||
|
||||
if (newFlow?.data?.nodes?.length === 0) return;
|
||||
|
||||
try {
|
||||
// updates flow in db
|
||||
const updatedFlow = await updateFlowInDatabase(newFlow);
|
||||
|
|
@ -615,14 +631,10 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
if (index !== -1) {
|
||||
newFlows[index] = {
|
||||
...newFlows[index],
|
||||
description: updatedFlow.description,
|
||||
data: updatedFlow.data,
|
||||
name: updatedFlow.name,
|
||||
};
|
||||
newFlows[index].description = newFlow.description ?? "";
|
||||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
}
|
||||
|
||||
return newFlows;
|
||||
});
|
||||
//update tabs state
|
||||
|
|
@ -644,9 +656,9 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
}
|
||||
|
||||
function saveComponent(component: NodeDataType) {
|
||||
function saveComponent(component: NodeDataType, override: boolean) {
|
||||
component.node!.official = false;
|
||||
return addFlow(true, createFlowComponent(component, version));
|
||||
return addFlow(true, createFlowComponent(component, version), override);
|
||||
}
|
||||
|
||||
function deleteComponent(key: string) {
|
||||
|
|
|
|||
|
|
@ -360,6 +360,16 @@ export async function postCustomComponent(
|
|||
return await api.post(`${BASE_URL_API}custom_component`, { code });
|
||||
}
|
||||
|
||||
export async function postCustomComponentUpdate(
|
||||
code: string,
|
||||
field: string
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
||||
code,
|
||||
field,
|
||||
});
|
||||
}
|
||||
|
||||
export async function onLogin(user: LoginType) {
|
||||
try {
|
||||
const response = await api.post(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import {
|
|||
// import "ace-builds/webpack-resolver";
|
||||
import CodeTabsComponent from "../../components/codeTabsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
|
||||
import {
|
||||
EXPORT_CODE_DIALOG,
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { TemplateVariableType } from "../../types/api";
|
||||
|
|
@ -99,15 +102,9 @@ const ApiModal = forwardRef(
|
|||
(templateField) =>
|
||||
templateField.charAt(0) !== "_" &&
|
||||
node.data.node.template[templateField].show &&
|
||||
(node.data.node.template[templateField].type === "str" ||
|
||||
node.data.node.template[templateField].type === "bool" ||
|
||||
node.data.node.template[templateField].type === "float" ||
|
||||
node.data.node.template[templateField].type === "code" ||
|
||||
node.data.node.template[templateField].type === "prompt" ||
|
||||
node.data.node.template[templateField].type === "file" ||
|
||||
node.data.node.template[templateField].type === "int" ||
|
||||
node.data.node.template[templateField].type === "dict" ||
|
||||
node.data.node.template[templateField].type === "NestedDict")
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
node.data.node.template[templateField].type
|
||||
)
|
||||
)
|
||||
.map((n, i) => {
|
||||
arrNodesWithValues.push(node["id"]);
|
||||
|
|
@ -146,9 +143,9 @@ const ApiModal = forwardRef(
|
|||
);
|
||||
|
||||
if (existingTweak) {
|
||||
existingTweak[tw][template["name"]] = changes as string;
|
||||
existingTweak[tw][template["name"]!] = changes as string;
|
||||
|
||||
if (existingTweak[tw][template["name"]] == template.value) {
|
||||
if (existingTweak[tw][template["name"]!] == template.value) {
|
||||
tweak.current.forEach((element) => {
|
||||
if (element[tw] && Object.keys(element[tw])?.length === 0) {
|
||||
tweak.current = tweak.current.filter((obj) => {
|
||||
|
|
@ -161,7 +158,7 @@ const ApiModal = forwardRef(
|
|||
} else {
|
||||
const newTweak = {
|
||||
[tw]: {
|
||||
[template["name"]]: changes,
|
||||
[template["name"]!]: changes,
|
||||
},
|
||||
} as uniqueTweakType;
|
||||
tweak.current.push(newTweak);
|
||||
|
|
|
|||
|
|
@ -36,10 +36,15 @@ function ConfirmationModal({
|
|||
size,
|
||||
open,
|
||||
onClose,
|
||||
onCancel,
|
||||
}: ConfirmationModalType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setModalOpen(open);
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onClose) onClose!(modalOpen);
|
||||
}, [modalOpen]);
|
||||
|
|
@ -86,6 +91,7 @@ function ConfirmationModal({
|
|||
className="mt-5"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (onCancel) onCancel();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import { limitScrollFieldsModal } from "../../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
limitScrollFieldsModal,
|
||||
} from "../../constants/constants";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
|
|
@ -123,7 +126,7 @@ const EditNodeModal = forwardRef(
|
|||
"edit-node-modal-box",
|
||||
nodeLength > limitScrollFieldsModal
|
||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
||||
: "overflow-hidden"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
{nodeLength > 0 && (
|
||||
|
|
@ -144,24 +147,9 @@ const EditNodeModal = forwardRef(
|
|||
(templateParam) =>
|
||||
templateParam.charAt(0) !== "_" &&
|
||||
myData.node?.template[templateParam].show &&
|
||||
(myData.node.template[templateParam]
|
||||
.type === "str" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "bool" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "float" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "code" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "prompt" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "file" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "int" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "dict" ||
|
||||
myData.node.template[templateParam]
|
||||
.type === "NestedDict")
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
myData.node.template[templateParam].type
|
||||
)
|
||||
)
|
||||
.map((templateParam, index) => (
|
||||
<TableRow key={index} className="h-10">
|
||||
|
|
@ -261,7 +249,7 @@ const EditNodeModal = forwardRef(
|
|||
value={
|
||||
myData.node!.template[
|
||||
templateParam
|
||||
].value.toString() === "{}"
|
||||
]?.value?.toString() === "{}"
|
||||
? {
|
||||
yourkey: "value",
|
||||
}
|
||||
|
|
@ -275,6 +263,7 @@ const EditNodeModal = forwardRef(
|
|||
].value = newValue;
|
||||
handleOnNewValue(newValue, templateParam);
|
||||
}}
|
||||
id="editnode-div-dict-input"
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
|
|
@ -380,6 +369,7 @@ const EditNodeModal = forwardRef(
|
|||
templateParam
|
||||
].value ?? "Choose an option"
|
||||
}
|
||||
id={"dropdown-edit-" + index}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export default function DictAreaModal({
|
|||
/>
|
||||
<div className="flex h-fit w-full justify-end">
|
||||
<Button
|
||||
data-testid="save-dict-button"
|
||||
className="mt-3"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ export default function GenericModal({
|
|||
{type === TypeModal.PROMPT && isEdit && !readonly ? (
|
||||
<Textarea
|
||||
id={"modal-" + id}
|
||||
data-testid={"modal-" + id}
|
||||
ref={divRefPrompt}
|
||||
className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
|
||||
value={inputValue}
|
||||
|
|
@ -252,6 +253,8 @@ export default function GenericModal({
|
|||
handleKeyDown(e, value, "");
|
||||
}}
|
||||
readOnly={readonly}
|
||||
id={"text-area-modal"}
|
||||
data-testid={"text-area-modal"}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
|
@ -308,6 +311,7 @@ export default function GenericModal({
|
|||
)}
|
||||
</div>
|
||||
<Button
|
||||
data-testid="genericModalBtnSave"
|
||||
id="genericModalBtnSave"
|
||||
disabled={readonly}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Button } from "../../components/ui/button";
|
|||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../contexts/storeContext";
|
||||
import {
|
||||
getStoreComponents,
|
||||
getStoreTags,
|
||||
|
|
@ -22,14 +23,17 @@ export default function ShareModal({
|
|||
children,
|
||||
open,
|
||||
setOpen,
|
||||
disabled,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
is_component: boolean;
|
||||
component: FlowType;
|
||||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}): JSX.Element {
|
||||
const { version, addFlow } = useContext(FlowsContext);
|
||||
const { hasApiKey } = useContext(StoreContext);
|
||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [name, setName] = useState(component?.name ?? "");
|
||||
|
|
@ -46,10 +50,12 @@ export default function ShareModal({
|
|||
|
||||
useEffect(() => {
|
||||
if (open || internalOpen) {
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
if (hasApiKey) {
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
}
|
||||
}
|
||||
}, [open, internalOpen]);
|
||||
}, [open, internalOpen, hasApiKey]);
|
||||
|
||||
function handleGetTags() {
|
||||
setLoadingTags(true);
|
||||
|
|
@ -113,7 +119,7 @@ export default function ShareModal({
|
|||
return (
|
||||
<BaseModal
|
||||
size="smaller-h-full"
|
||||
open={open ?? internalOpen}
|
||||
open={(!disabled && open) ?? internalOpen}
|
||||
setOpen={setOpen ?? internalSetOpen}
|
||||
>
|
||||
<BaseModal.Trigger>{children ? children : <></>}</BaseModal.Trigger>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export default function Page({
|
|||
saveFlow,
|
||||
setTabsState,
|
||||
tabId,
|
||||
flows,
|
||||
} = useContext(FlowsContext);
|
||||
const {
|
||||
types,
|
||||
|
|
@ -144,7 +145,6 @@ export default function Page({
|
|||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [position, lastCopiedSelection, lastSelection]);
|
||||
|
||||
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
||||
|
||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||
|
|
@ -181,6 +181,8 @@ export default function Page({
|
|||
const [seconds, setSeconds] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const index = flows.findIndex((flowId) => flowId.id === flow.id);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setSeconds((prevSeconds) => {
|
||||
let updatedSeconds = prevSeconds + 1;
|
||||
|
|
@ -188,7 +190,7 @@ export default function Page({
|
|||
if (updatedSeconds % 30 === 0) {
|
||||
saveFlow(
|
||||
{
|
||||
...flow!,
|
||||
...flows[index]!,
|
||||
data: reactFlowInstance
|
||||
? reactFlowInstance!.toObject()
|
||||
: flow!.data,
|
||||
|
|
@ -318,9 +320,9 @@ export default function Page({
|
|||
);
|
||||
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance!.project({
|
||||
x: event.clientX - reactflowBounds!.left,
|
||||
y: event.clientY - reactflowBounds!.top,
|
||||
const position = reactFlowInstance!.screenToFlowPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Input } from "../../../../components/ui/input";
|
|||
import { Separator } from "../../../../components/ui/separator";
|
||||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import ApiModal from "../../../../modals/ApiModal";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
|
|
@ -29,6 +30,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
useContext(typesContext);
|
||||
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
|
||||
useContext(FlowsContext);
|
||||
const { hasApiKey, validApiKey } = useContext(StoreContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [dataFilter, setFilterData] = useState(data);
|
||||
const [search, setSearch] = useState("");
|
||||
|
|
@ -179,25 +181,56 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
}, [getFilterEdge, data]);
|
||||
|
||||
const ModalMemo = useMemo(
|
||||
() => (
|
||||
<ShareModal is_component={false} component={flow!}>
|
||||
<ShadTooltip content="Share" side="top">
|
||||
<div className={classNames("extra-side-bar-buttons")}>
|
||||
<IconComponent name="Share2" className="side-bar-button-size" />
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</ShareModal>
|
||||
),
|
||||
[]
|
||||
() =>
|
||||
!hasApiKey || !validApiKey ? (
|
||||
<button
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
className={classNames(
|
||||
"extra-side-bar-buttons",
|
||||
!hasApiKey || !validApiKey ? "button-disable cursor-default" : ""
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
className={classNames(
|
||||
"side-bar-button-size",
|
||||
!hasApiKey || !validApiKey ? "extra-side-bar-save-disable" : ""
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<ShareModal
|
||||
is_component={false}
|
||||
component={flow!}
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
>
|
||||
<button
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
className={classNames(
|
||||
"extra-side-bar-buttons",
|
||||
!hasApiKey || !validApiKey ? "button-disable cursor-default" : ""
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
className={classNames(
|
||||
"side-bar-button-size",
|
||||
!hasApiKey || !validApiKey ? "extra-side-bar-save-disable" : ""
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</ShareModal>
|
||||
),
|
||||
[hasApiKey, validApiKey]
|
||||
);
|
||||
|
||||
const ExportMemo = useMemo(
|
||||
() => (
|
||||
<ExportModal>
|
||||
<ShadTooltip content="Export" side="top">
|
||||
<div className={classNames("extra-side-bar-buttons")}>
|
||||
<button className={classNames("extra-side-bar-buttons")}>
|
||||
<IconComponent name="FileDown" className="side-bar-button-size" />
|
||||
</div>
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
</ExportModal>
|
||||
),
|
||||
|
|
@ -276,8 +309,9 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</div>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
|
||||
<div className="side-bar-button">{ModalMemo}</div>
|
||||
<ShadTooltip content="Share" side="top" styleClasses="cursor-default">
|
||||
<div className="side-bar-button">{ModalMemo}</div>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="side-bar-search-div-placement">
|
||||
|
|
@ -305,7 +339,15 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
<div className="side-bar-components-div-arrangement">
|
||||
{Object.keys(dataFilter)
|
||||
.sort()
|
||||
.sort((a, b) => {
|
||||
if (a.toLowerCase() === "saved_components") {
|
||||
return -1;
|
||||
} else if (b.toLowerCase() === "saved_components") {
|
||||
return 1;
|
||||
} else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
})
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<DisclosureComponent
|
||||
|
|
|
|||
|
|
@ -88,17 +88,17 @@ export default function SidebarDraggableComponent({
|
|||
}}
|
||||
>
|
||||
<div
|
||||
data-testid={sectionName + display_name}
|
||||
id={sectionName + display_name}
|
||||
className="side-bar-components-div-form"
|
||||
>
|
||||
<span className="side-bar-components-text">{display_name}</span>
|
||||
<div>
|
||||
<SelectTrigger>
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon "
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon "
|
||||
/>
|
||||
<SelectTrigger></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={"download"}>
|
||||
<div className="flex">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
SelectTrigger,
|
||||
} from "../../../../components/ui/select-custom";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
import ConfirmationModal from "../../../../modals/ConfirmationModal";
|
||||
import EditNodeModal from "../../../../modals/EditNodeModal";
|
||||
import ShareModal from "../../../../modals/shareModal";
|
||||
import { nodeToolbarPropsType } from "../../../../types/components";
|
||||
|
|
@ -49,6 +51,7 @@ export default function NodeToolbarComponent({
|
|||
);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { getNodeId } = useContext(FlowsContext);
|
||||
const { hasApiKey, validApiKey } = useContext(StoreContext);
|
||||
|
||||
function canMinimize() {
|
||||
let countHandles: number = 0;
|
||||
|
|
@ -61,17 +64,18 @@ export default function NodeToolbarComponent({
|
|||
const isMinimal = canMinimize();
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
||||
const { paste, saveComponent, version } = useContext(FlowsContext);
|
||||
const { paste, saveComponent, version, flows } = useContext(FlowsContext);
|
||||
const reactFlowInstance = useReactFlow();
|
||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState("");
|
||||
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
||||
|
||||
const [flowComponent, setFlowComponent] = useState<FlowType>();
|
||||
|
||||
useEffect(() => {
|
||||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||
}, [data]);
|
||||
}, [data, showModalAdvanced]);
|
||||
|
||||
const handleSelectChange = (event) => {
|
||||
switch (event) {
|
||||
|
|
@ -86,10 +90,10 @@ export default function NodeToolbarComponent({
|
|||
downloadNode(createFlowComponent(cloneDeep(data), version));
|
||||
break;
|
||||
case "Share":
|
||||
setShowconfirmShare(true);
|
||||
if (hasApiKey) setShowconfirmShare(true);
|
||||
break;
|
||||
case "SaveAll":
|
||||
saveComponent(cloneDeep(data));
|
||||
saveComponent(cloneDeep(data), false);
|
||||
break;
|
||||
case "disabled":
|
||||
break;
|
||||
|
|
@ -97,9 +101,16 @@ export default function NodeToolbarComponent({
|
|||
updateFlowPosition(position, data.node?.flow!);
|
||||
expandGroupNode(data, reactFlowInstance, getNodeId);
|
||||
break;
|
||||
case "override":
|
||||
setShowOverrideModal(true);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const isSaved = flows.some((flow) =>
|
||||
Object.values(flow).includes(data.node?.display_name!)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-26 h-10">
|
||||
|
|
@ -174,10 +185,7 @@ export default function NodeToolbarComponent({
|
|||
<div
|
||||
data-testid="more-options-modal"
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
|
||||
(nodeLength == 0
|
||||
? " text-muted-foreground"
|
||||
: " text-foreground")
|
||||
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
@ -191,7 +199,7 @@ export default function NodeToolbarComponent({
|
|||
<SelectContent>
|
||||
{nodeLength > 0 && (
|
||||
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
||||
<div className="flex">
|
||||
<div className="flex" data-testid="edit-button-modal">
|
||||
<IconComponent
|
||||
name="Settings2"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
|
|
@ -201,16 +209,29 @@ export default function NodeToolbarComponent({
|
|||
</SelectItem>
|
||||
)}
|
||||
|
||||
<SelectItem value={"SaveAll"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
<SelectItem value={"Share"}>
|
||||
{isSaved ? (
|
||||
<SelectItem value={"override"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
) : (
|
||||
<SelectItem value={"SaveAll"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
)}
|
||||
|
||||
<SelectItem disabled={!hasApiKey || !validApiKey} value={"Share"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
|
|
@ -252,6 +273,33 @@ export default function NodeToolbarComponent({
|
|||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<ConfirmationModal
|
||||
asChild
|
||||
open={showOverrideModal}
|
||||
title={`Replace ${data.node?.display_name}`}
|
||||
titleHeader={`Please, confirm your save actions`}
|
||||
modalContentTitle="Attention!"
|
||||
cancelText="New"
|
||||
confirmationText="Replace"
|
||||
icon={"SaveAll"}
|
||||
index={6}
|
||||
onConfirm={(index, user) => {
|
||||
saveComponent(cloneDeep(data), true);
|
||||
}}
|
||||
onClose={setShowOverrideModal}
|
||||
onCancel={() => saveComponent(cloneDeep(data), false)}
|
||||
>
|
||||
<ConfirmationModal.Content>
|
||||
<span>
|
||||
It seems {data.node?.display_name} already exists. Replacing it
|
||||
will switch the current component. Proceed with replacement?
|
||||
</span>
|
||||
</ConfirmationModal.Content>
|
||||
<ConfirmationModal.Trigger>
|
||||
<></>
|
||||
</ConfirmationModal.Trigger>
|
||||
</ConfirmationModal>
|
||||
<EditNodeModal
|
||||
data={data}
|
||||
nodeLength={nodeLength}
|
||||
|
|
|
|||
|
|
@ -275,8 +275,18 @@ export default function StorePage(): JSX.Element {
|
|||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="all">All</SelectItem>
|
||||
<SelectItem value="createdbyme">Created By Me</SelectItem>
|
||||
<SelectItem value="likedbyme">Liked By Me</SelectItem>
|
||||
<SelectItem
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
value="createdbyme"
|
||||
>
|
||||
Created By Me
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
value="likedbyme"
|
||||
>
|
||||
Liked By Me
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
@apply pointer-events-none;
|
||||
}
|
||||
.extra-side-bar-buttons {
|
||||
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground shadow-sm ring-1 ring-inset ring-input transition-all duration-500 ease-in-out;
|
||||
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground shadow-sm ring-1 ring-inset ring-input transition-all duration-500 ease-in-out;
|
||||
}
|
||||
.extra-side-bar-buttons:hover {
|
||||
@apply hover:bg-muted;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export type DropDownComponentType = {
|
|||
editNode?: boolean;
|
||||
apiModal?: boolean;
|
||||
numberOfOptions?: number;
|
||||
id?: string;
|
||||
};
|
||||
export type ParameterComponentType = {
|
||||
data: NodeDataType;
|
||||
|
|
@ -67,6 +68,7 @@ export type KeyPairListComponentType = {
|
|||
disabled: boolean;
|
||||
editNode?: boolean;
|
||||
duplicateKey?: boolean;
|
||||
editNodeModal?: boolean;
|
||||
};
|
||||
|
||||
export type DictComponentType = {
|
||||
|
|
@ -74,6 +76,7 @@ export type DictComponentType = {
|
|||
onChange: (value) => void;
|
||||
disabled: boolean;
|
||||
editNode?: boolean;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type TextAreaComponentType = {
|
||||
|
|
@ -214,6 +217,7 @@ export type IconComponentProps = {
|
|||
iconColor?: string;
|
||||
onClick?: () => void;
|
||||
stroke?: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type InputProps = {
|
||||
|
|
@ -277,6 +281,7 @@ export type PaginatorComponentType = {
|
|||
};
|
||||
|
||||
export type ConfirmationModalType = {
|
||||
onCancel?: () => void;
|
||||
title: string;
|
||||
titleHeader: string;
|
||||
asChild?: boolean;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ export type FlowsContextType = {
|
|||
removeFlow: (id: string) => void;
|
||||
addFlow: (
|
||||
newProject: boolean,
|
||||
flow?: FlowType
|
||||
flow?: FlowType,
|
||||
override?: boolean
|
||||
) => Promise<String | undefined>;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => string;
|
||||
|
|
@ -44,7 +45,10 @@ export type FlowsContextType = {
|
|||
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
||||
setTweak: (tweak: tweakType) => tweakType | void;
|
||||
getTweak: tweakType;
|
||||
saveComponent: (component: NodeDataType) => Promise<String | undefined>;
|
||||
saveComponent: (
|
||||
component: NodeDataType,
|
||||
override: boolean
|
||||
) => Promise<String | undefined>;
|
||||
deleteComponent: (key: string) => void;
|
||||
version: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import {
|
|||
XYPosition,
|
||||
} from "reactflow";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { specialCharsRegex } from "../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
specialCharsRegex,
|
||||
} from "../constants/constants";
|
||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
||||
import {
|
||||
FlowType,
|
||||
|
|
@ -634,15 +637,7 @@ function updateGroupNodeTemplate(template: APITemplateType) {
|
|||
let type = template[key].type;
|
||||
let input_types = template[key].input_types;
|
||||
if (
|
||||
(type === "str" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict") &&
|
||||
LANGFLOW_SUPPORTED_TYPES.has(type) &&
|
||||
!template[key].required &&
|
||||
!input_types
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import {
|
|||
Pencil,
|
||||
Plus,
|
||||
Redo,
|
||||
RefreshCcw,
|
||||
Rocket,
|
||||
Save,
|
||||
SaveAll,
|
||||
|
|
@ -195,7 +196,7 @@ export const nodeNames: { [char: string]: string } = {
|
|||
agents: "Agents",
|
||||
tools: "Tools",
|
||||
memories: "Memories",
|
||||
saved_components: "Saved Components",
|
||||
saved_components: "Saved",
|
||||
advanced: "Advanced",
|
||||
chat: "Chat",
|
||||
embeddings: "Embeddings",
|
||||
|
|
@ -354,4 +355,5 @@ export const nodeIconsLucide: iconsType = {
|
|||
Heart,
|
||||
Link,
|
||||
ToyBrick,
|
||||
RefreshCcw,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ test("CodeAreaModalComponent", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sidePythonFunctionTool"]')
|
||||
.getByTestId("toolsPythonFunctionTool")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
@ -34,8 +34,9 @@ test("CodeAreaModalComponent", async ({ page }) => {
|
|||
await page
|
||||
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("genericModalBtnSave").click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showcode"]').click();
|
||||
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
|
||||
|
|
@ -114,8 +115,8 @@ test("CodeAreaModalComponent", async ({ page }) => {
|
|||
await page
|
||||
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showcode"]').click();
|
||||
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
|
||||
|
|
|
|||
104
src/frontend/tests/end-to-end/dropdownComponent.spec.ts
Normal file
104
src/frontend/tests/end-to-end/dropdownComponent.spec.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("dropDownComponent", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("amazon");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.getByTestId("llmsAmazon Bedrock")
|
||||
.first()
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.getByTestId("dropdown-2-display").click();
|
||||
await page.getByTestId("ai21.j2-grande-instruct-0-option").click();
|
||||
|
||||
let value = await page.getByTestId("dropdown-2-display").innerText();
|
||||
if (value !== "ai21.j2-grande-instruct") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.getByTestId("dropdown-2-display").click();
|
||||
await page.getByTestId("ai21.j2-jumbo-instruct-1-option").click();
|
||||
|
||||
value = await page.getByTestId("dropdown-2-display").innerText();
|
||||
if (value !== "ai21.j2-jumbo-instruct") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
value = await page.getByTestId("dropdown-edit-1-display").innerText();
|
||||
if (value !== "ai21.j2-jumbo-instruct") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
// showcredentials_profile_name
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showmodel_id
|
||||
await page.locator('//*[@id="showmodel_id"]').click();
|
||||
expect(await page.locator('//*[@id="showmodel_id"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showcredentials_profile_name
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmodel_id
|
||||
await page.locator('//*[@id="showmodel_id"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_id"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showcredentials_profile_name
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showmodel_id
|
||||
await page.locator('//*[@id="showmodel_id"]').click();
|
||||
expect(await page.locator('//*[@id="showmodel_id"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showcredentials_profile_name
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmodel_id
|
||||
await page.locator('//*[@id="showmodel_id"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_id"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.getByTestId("dropdown-edit-1-display").click();
|
||||
await page.getByTestId("ai21.j2-ultra-v1-5-option").click();
|
||||
|
||||
value = await page.getByTestId("dropdown-edit-1-display").innerText();
|
||||
if (value !== "ai21.j2-ultra-v1") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
||||
value = await page.getByTestId("dropdown-2-display").innerText();
|
||||
if (value !== "ai21.j2-ultra-v1") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
|
@ -42,6 +42,7 @@ test("FloatComponent", async ({ page }) => {
|
|||
)
|
||||
.click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showcache"]').click();
|
||||
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeTruthy();
|
||||
|
|
@ -385,7 +386,8 @@ test("FloatComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
// showtemperature
|
||||
await page.locator('//*[@id="showtemperature"]').click();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.describe("Flow Page tests", () => {
|
|||
.filter({ hasText: /^Custom Component$/ })
|
||||
.nth(4)
|
||||
.dragTo(page.locator(".react-flow__pane"));
|
||||
await page.locator("div:nth-child(4) > .extra-side-bar-buttons").click();
|
||||
await page.locator(".success-alert").click();
|
||||
await page.getByTestId("icon-ExternalLink").click();
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ test("IntComponent", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sideGET Request"]')
|
||||
.getByTestId("utilitiesGET Request")
|
||||
.first()
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
@ -30,7 +31,7 @@ test("IntComponent", async ({ page }) => {
|
|||
}
|
||||
|
||||
await page.locator('//*[@id="int-input-2"]').click();
|
||||
await page.locator('//*[@id="int-input-2"]').fill("-3");
|
||||
await page.locator('//*[@id="int-input-2"]').fill("0");
|
||||
|
||||
value = await page.locator('//*[@id="int-input-2"]').inputValue();
|
||||
|
||||
|
|
@ -41,8 +42,8 @@ test("IntComponent", async ({ page }) => {
|
|||
await page
|
||||
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
value = await page.locator('//*[@id="int-input-1"]').inputValue();
|
||||
|
||||
|
|
@ -80,8 +81,8 @@ test("IntComponent", async ({ page }) => {
|
|||
await page
|
||||
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showtimeout"]').click();
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sideCSVLoader"]')
|
||||
.getByTestId("documentloadersCSVLoader")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
@ -66,8 +66,8 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showfile_path"]').click();
|
||||
expect(
|
||||
|
|
@ -87,8 +87,8 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showfile_path"]').click();
|
||||
expect(
|
||||
|
|
@ -99,11 +99,11 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
await page.locator('//*[@id="showmetadata"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.locator('//*[@id="keypair0"]').click();
|
||||
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
|
||||
await page.locator('//*[@id="keypair100"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair0"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair0"]').fill("testtesttesttest");
|
||||
await page.locator('//*[@id="editNodekeypair100"]').click();
|
||||
await page
|
||||
.locator('//*[@id="keypair100"]')
|
||||
.locator('//*[@id="editNodekeypair100"]')
|
||||
.fill("test test test test test test");
|
||||
|
||||
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
|
||||
|
|
@ -112,24 +112,24 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
await plusButtonLocator.click();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="keypair1"]').click();
|
||||
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
|
||||
await page.locator('//*[@id="keypair101"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair1"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair1"]').fill("testtesttesttest1");
|
||||
await page.locator('//*[@id="editNodekeypair101"]').first().click();
|
||||
await page
|
||||
.locator('//*[@id="keypair101"]')
|
||||
.locator('//*[@id="editNodekeypair101"]')
|
||||
.fill("testtesttesttesttesttest1");
|
||||
await page.locator('//*[@id="plusbtn1"]').click();
|
||||
await page.locator('//*[@id="editNodeplusbtn1"]').click();
|
||||
|
||||
await page.locator('//*[@id="keypair2"]').click();
|
||||
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
|
||||
await page.locator('//*[@id="keypair102"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair2"]').click();
|
||||
await page.locator('//*[@id="editNodekeypair2"]').fill("testtesttesttest2");
|
||||
await page.locator('//*[@id="editNodekeypair102"]').click();
|
||||
await page
|
||||
.locator('//*[@id="keypair102"]')
|
||||
.locator('//*[@id="editNodekeypair102"]')
|
||||
.fill("testtesttesttesttesttest2");
|
||||
|
||||
await page.locator('//*[@id="minusbtn1"]').click();
|
||||
await page.locator('//*[@id="editNodeminusbtn1"]').click();
|
||||
|
||||
const keyPairVerification = page.locator('//*[@id="keypair102"]');
|
||||
const keyPairVerification = page.locator('//*[@id="editNodekeypair102"]');
|
||||
const elementKeyCount = await keyPairVerification.count();
|
||||
|
||||
if (elementKeyCount === 0) {
|
||||
|
|
@ -143,8 +143,8 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
if (
|
||||
key1 === "testtesttesttest" &&
|
||||
value1 === "test test test test test test" &&
|
||||
key2 === "testtesttesttest2" &&
|
||||
value2 === "testtesttesttesttesttest2"
|
||||
key2 === "testtesttesttest1" &&
|
||||
value2 === "testtesttesttesttesttest1"
|
||||
) {
|
||||
expect(true).toBeTruthy();
|
||||
} else {
|
||||
|
|
|
|||
239
src/frontend/tests/end-to-end/nestedComponent.spec.ts
Normal file
239
src/frontend/tests/end-to-end/nestedComponent.spec.ts
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("NestedComponent", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("pinecone");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.getByTestId("vectorstoresPinecone")
|
||||
.first()
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showsearch_kwargs
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showsearch_kwargs
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showsearch_kwargs
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showsearch_kwargs
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showsearch_kwargs
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
||||
await page.getByTestId("div-dict-input").click();
|
||||
|
||||
const spanElement = await page
|
||||
.locator('//*[@id="radix-:r4i:"]/div[2]/div/div/code/div/div/span[1]')
|
||||
.innerHTML();
|
||||
|
||||
if (spanElement !== "yourkey") {
|
||||
expect(true).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
|
@ -13,19 +13,20 @@ test("PromptTemplateComponent", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sidePromptTemplate"]')
|
||||
.locator('//*[@id="promptsPromptTemplate"]')
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.locator('//*[@id="prompt-input-4"]').click();
|
||||
await page
|
||||
.locator('//*[@id="modal-prompt-input-4"]')
|
||||
.fill("{prompt} example {prompt1}");
|
||||
await page.getByTestId("prompt-input-4-ExternalLink").click();
|
||||
// await page.getByTestId("edit-prompt-sanitized").click();
|
||||
|
||||
let value = await page
|
||||
.locator('//*[@id="modal-prompt-input-4"]')
|
||||
.inputValue();
|
||||
await page
|
||||
.getByTestId("modal-prompt-input-4")
|
||||
.fill("{prompt} example {prompt1}");
|
||||
// await page.getByTestId("edit-prompt-sanitized").click();
|
||||
|
||||
let value = await page.getByTestId("modal-prompt-input-4").inputValue();
|
||||
|
||||
if (value != "{prompt} example {prompt1}") {
|
||||
expect(false).toBeTruthy();
|
||||
|
|
@ -43,33 +44,38 @@ test("PromptTemplateComponent", async ({ page }) => {
|
|||
|
||||
await page.locator('//*[@id="genericModalBtnSave"]').click();
|
||||
|
||||
await page.locator('//*[@id="textarea-7"]').click();
|
||||
await page.locator('//*[@id="textarea-7"]').fill("prompt_value_!@#!@#");
|
||||
await page.getByTestId("textarea-4-ExternalLink").click();
|
||||
await page.getByTestId("text-area-modal").fill("prompt_value_!@#!@#");
|
||||
|
||||
value = await page.locator('//*[@id="textarea-7"]').inputValue();
|
||||
value = await page.getByTestId("text-area-modal").inputValue();
|
||||
|
||||
if (value != "prompt_value_!@#!@#") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="textarea-8"]').click();
|
||||
await page.getByTestId("genericModalBtnSave").click();
|
||||
|
||||
await page.getByTestId("textarea-5-ExternalLink").click();
|
||||
await page
|
||||
.locator('//*[@id="textarea-8"]')
|
||||
.getByTestId("text-area-modal")
|
||||
.fill("prompt_name_test_123123!@#!@#");
|
||||
|
||||
value = await page.locator('//*[@id="textarea-8"]').inputValue();
|
||||
value = await page.getByTestId("text-area-modal").inputValue();
|
||||
|
||||
if (value != "prompt_name_test_123123!@#!@#") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
value = await page.locator('//*[@id="prompt-input-4"]').innerText();
|
||||
value = await page.getByTestId("text-area-modal").inputValue();
|
||||
|
||||
if (value != "{prompt} example {prompt1}") {
|
||||
if (value != "prompt_name_test_123123!@#!@#") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
await page.getByTestId("genericModalBtnSave").click();
|
||||
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
value = await page.locator('//*[@id="textarea-edit-1"]').inputValue();
|
||||
|
||||
|
|
@ -146,7 +152,8 @@ test("PromptTemplateComponent", async ({ page }) => {
|
|||
)
|
||||
.click();
|
||||
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showprompt1"]').click();
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ test("InputComponent", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sideChroma"]')
|
||||
.getByTestId("vectorstoresChroma")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
@ -44,7 +44,8 @@ test("InputComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showchroma_server_cors_allow_origins"]').click();
|
||||
expect(
|
||||
|
|
@ -154,7 +155,8 @@ test("InputComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showcollection_name"]').click();
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ test("ToggleComponent", async ({ page }) => {
|
|||
|
||||
await page.waitForTimeout(2000);
|
||||
await page
|
||||
.locator('//*[@id="sideDirectoryLoader"]')
|
||||
.getByTestId("documentloadersDirectoryLoader")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
@ -32,8 +32,8 @@ test("ToggleComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div/div/div[2]/div/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showload_hidden"]').click();
|
||||
expect(
|
||||
|
|
@ -62,8 +62,8 @@ test("ToggleComponent", async ({ page }) => {
|
|||
'//*[@id="react-flow-id"]/div[1]/div[1]/div/div/div[2]/div/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="toggle-edit-1"]').isChecked()
|
||||
|
|
@ -144,8 +144,8 @@ test("ToggleComponent", async ({ page }) => {
|
|||
await page
|
||||
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
await page.locator('//*[@id="showload_hidden"]').click();
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue