Merge branch 'better_io' into state_theories

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-03 22:26:39 -03:00
commit e8d047239e
46 changed files with 593 additions and 327 deletions

View file

@ -22,10 +22,10 @@ from langflow.services.auth.utils import get_current_active_user
from langflow.services.chat.service import ChatService from langflow.services.chat.service import ChatService
from langflow.services.deps import get_chat_service, get_session, get_session_service from langflow.services.deps import get_chat_service, get_session, get_session_service
from langflow.services.monitor.utils import log_vertex_build from langflow.services.monitor.utils import log_vertex_build
from langflow.services.session.service import SessionService
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.graph.vertex.types import ChatVertex from langflow.graph.vertex.types import ChatVertex
from langflow.services.session.service import SessionService
router = APIRouter(tags=["Chat"]) router = APIRouter(tags=["Chat"])
@ -49,7 +49,8 @@ async def try_running_celery_task(vertex, user_id):
@router.get("/build/{flow_id}/vertices", response_model=VerticesOrderResponse) @router.get("/build/{flow_id}/vertices", response_model=VerticesOrderResponse)
async def get_vertices( async def get_vertices(
flow_id: str, flow_id: str,
component_id: Optional[str] = None, stop_component_id: Optional[str] = None,
start_component_id: Optional[str] = None,
chat_service: "ChatService" = Depends(get_chat_service), chat_service: "ChatService" = Depends(get_chat_service),
session=Depends(get_session), session=Depends(get_session),
): ):
@ -60,9 +61,9 @@ async def get_vertices(
if cache := chat_service.get_cache(flow_id): if cache := chat_service.get_cache(flow_id):
graph = cache.get("result") graph = cache.get("result")
graph = build_and_cache_graph(flow_id, session, chat_service, graph) graph = build_and_cache_graph(flow_id, session, chat_service, graph)
if component_id: if stop_component_id or start_component_id:
try: try:
vertices = graph.sort_vertices(component_id) vertices = graph.sort_vertices(stop_component_id, start_component_id)
except Exception as exc: except Exception as exc:
logger.error(exc) logger.error(exc)
vertices = graph.sort_vertices() vertices = graph.sort_vertices()
@ -94,6 +95,7 @@ async def build_vertex(
"""Build a vertex instead of the entire graph.""" """Build a vertex instead of the entire graph."""
{"inputs": {"input_value": "some value"}} {"inputs": {"input_value": "some value"}}
start_time = time.perf_counter() start_time = time.perf_counter()
next_vertices_ids = []
try: try:
start_time = time.perf_counter() start_time = time.perf_counter()
cache = chat_service.get_cache(flow_id) cache = chat_service.get_cache(flow_id)
@ -123,6 +125,10 @@ async def build_vertex(
artifacts = vertex.artifacts artifacts = vertex.artifacts
else: else:
raise ValueError(f"No result found for vertex {vertex_id}") raise ValueError(f"No result found for vertex {vertex_id}")
next_vertices_ids = vertex.successors_ids
next_vertices_ids = [
v for v in next_vertices_ids if graph.should_run_vertex(v)
]
result_data_response = ResultDataResponse(**result_dict.model_dump()) result_data_response = ResultDataResponse(**result_dict.model_dump())
@ -160,9 +166,16 @@ async def build_vertex(
graph.reset_activated_vertices() graph.reset_activated_vertices()
chat_service.set_cache(flow_id, graph) chat_service.set_cache(flow_id, graph)
# graph.stop_vertex tells us if the user asked
# to stop the build of the graph at a certain vertex
# if it is in next_vertices_ids, we need to remove other
# vertices from next_vertices_ids
if graph.stop_vertex and graph.stop_vertex in next_vertices_ids:
next_vertices_ids = [graph.stop_vertex]
build_response = VertexBuildResponse( build_response = VertexBuildResponse(
inactivated_vertices=inactivated_vertices, inactivated_vertices=inactivated_vertices,
activated_layers=activated_layers, next_vertices_ids=next_vertices_ids,
valid=valid, valid=valid,
params=params, params=params,
id=vertex.id, id=vertex.id,

View file

@ -216,7 +216,7 @@ class ApiKeyCreateRequest(BaseModel):
class VerticesOrderResponse(BaseModel): class VerticesOrderResponse(BaseModel):
ids: List[List[str]] ids: List[str]
run_id: UUID run_id: UUID
@ -230,7 +230,7 @@ class ResultDataResponse(BaseModel):
class VertexBuildResponse(BaseModel): class VertexBuildResponse(BaseModel):
id: Optional[str] = None id: Optional[str] = None
inactivated_vertices: Optional[List[str]] = None inactivated_vertices: Optional[List[str]] = None
activated_layers: Optional[List[List[str]]] = None next_vertices_ids: Optional[List[str]] = None
valid: bool valid: bool
params: Optional[str] params: Optional[str]
"""JSON string of the params.""" """JSON string of the params."""

View file

@ -31,16 +31,12 @@ class ConversationChainComponent(CustomComponent):
chain = ConversationChain(llm=llm) chain = ConversationChain(llm=llm)
else: else:
chain = ConversationChain(llm=llm, memory=memory) chain = ConversationChain(llm=llm, memory=memory)
result = chain.invoke({chain.input_key: input_value}) result = chain.invoke(inputs)
# result is an AIMessage which is a subclass of BaseMessage
# We need to check if it is a string or a BaseMessage
result_str: Text = ""
if hasattr(result, "content") and isinstance(result.content, str): if hasattr(result, "content") and isinstance(result.content, str):
result_str = result.content result = result.content
elif isinstance(result, str): elif isinstance(result, str):
result_str = result result = result
else: else:
# is dict result = result.get("response")
result_str = Text(result.get("response")) self.status = result
self.status = result_str return result
return result_str

View file

@ -0,0 +1,48 @@
# Implement ShouldRunNext component
from langchain_core.prompts import PromptTemplate
from langflow import CustomComponent
from langflow.field_typing import BaseLanguageModel, Prompt
class ShouldRunNext(CustomComponent):
display_name = "Should Run Next"
description = "Decides whether to run the next component."
def build_config(self):
return {
"prompt": {
"display_name": "Prompt",
"info": "The prompt to use for the decision. It should generate a boolean response (True or False).",
},
"llm": {
"display_name": "LLM",
"info": "The language model to use for the decision.",
},
}
def build(self, template: Prompt, llm: BaseLanguageModel, **kwargs) -> bool:
# This is a simple component that always returns True
prompt_template = PromptTemplate.from_template(template)
attributes_to_check = ["text", "page_content"]
for key, value in kwargs.items():
for attribute in attributes_to_check:
if hasattr(value, attribute):
kwargs[key] = getattr(value, attribute)
chain = prompt_template | llm
result = chain.invoke(kwargs)
if hasattr(result, "content") and isinstance(result.content, str):
result = result.content
elif isinstance(result, str):
result = result
else:
result = result.get("response")
if result.lower() not in ["true", "false"]:
raise ValueError("The prompt should generate a boolean response (True or False).")
# The string should be the words true or false
# if not raise an error
bool_result = result.lower() == "true"
return bool_result

View file

@ -1,5 +1,6 @@
import asyncio import asyncio
from collections import defaultdict, deque from collections import defaultdict, deque
from itertools import chain
from typing import TYPE_CHECKING, Dict, Generator, List, Optional, Type, Union from typing import TYPE_CHECKING, Dict, Generator, List, Optional, Type, Union
from langchain.chains.base import Chain from langchain.chains.base import Chain
@ -59,6 +60,11 @@ class Graph:
self._edges = self._graph_data["edges"] self._edges = self._graph_data["edges"]
self.inactivated_vertices: set = set() self.inactivated_vertices: set = set()
self.activated_layers: List[List[str]] = [] self.activated_layers: List[List[str]] = []
self.vertices_layers = []
self.vertices_to_run = set()
self.stop_vertex = None
self.inactive_vertices: set = set()
self.edges: List[ContractEdge] = [] self.edges: List[ContractEdge] = []
self.vertices: List[Vertex] = [] self.vertices: List[Vertex] = []
self._build_graph() self._build_graph()
@ -191,6 +197,14 @@ class Graph:
outputs.extend(run_outputs) outputs.extend(run_outputs)
return outputs return outputs
# vertices_layers is a list of lists ordered by the order the vertices
# should be built.
# We need to create a new method that will take the vertices_layers
# and return the next vertex to be built.
def next_vertex_to_build(self):
"""Returns the next vertex to be built."""
yield from chain.from_iterable(self.vertices_layers)
@property @property
def metadata(self): def metadata(self):
return { return {
@ -336,9 +350,14 @@ class Graph:
# Add new vertices # Add new vertices
for vertex_id in new_vertex_ids: for vertex_id in new_vertex_ids:
new_vertex = other.get_vertex(vertex_id) new_vertex = other.get_vertex(vertex_id)
new_vertex.graph = self
self._add_vertex(new_vertex) self._add_vertex(new_vertex)
# Now update the edges
for vertex_id in new_vertex_ids:
new_vertex = other.get_vertex(vertex_id)
self._update_edges(new_vertex)
new_vertex.graph = self
# Update existing vertices that have changed # Update existing vertices that have changed
for vertex_id in existing_vertex_ids.intersection(other_vertex_ids): for vertex_id in existing_vertex_ids.intersection(other_vertex_ids):
self_vertex = self.get_vertex(vertex_id) self_vertex = self.get_vertex(vertex_id)
@ -385,12 +404,24 @@ class Graph:
_vertex._build_params() _vertex._build_params()
def _add_vertex(self, vertex: Vertex) -> None: def _add_vertex(self, vertex: Vertex) -> None:
"""Adds a new vertex to the graph.""" """Adds a vertex to the graph."""
self.vertices.append(vertex) self.vertices.append(vertex)
self.vertex_map[vertex.id] = vertex self.vertex_map[vertex.id] = vertex
def add_vertex(self, vertex: Vertex) -> None:
"""Adds a new vertex to the graph."""
self._add_vertex(vertex)
self._update_edges(vertex)
def _update_edges(self, vertex: Vertex) -> None:
"""Updates the edges of a vertex."""
# Vertex has edges, so we need to update the edges # Vertex has edges, so we need to update the edges
for edge in vertex.edges: for edge in vertex.edges:
if edge.source_id in self.vertex_map and edge.target_id in self.vertex_map: if (
edge not in self.edges
and edge.source_id in self.vertex_map
and edge.target_id in self.vertex_map
):
self.edges.append(edge) self.edges.append(edge)
def _build_graph(self) -> None: def _build_graph(self) -> None:
@ -722,7 +753,7 @@ class Graph:
) )
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}" return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
def sort_up_to_vertex(self, vertex_id: str) -> List[Vertex]: def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
"""Cuts the graph up to a given vertex and sorts the resulting subgraph.""" """Cuts the graph up to a given vertex and sorts the resulting subgraph."""
# Initial setup # Initial setup
visited = set() # To keep track of visited vertices visited = set() # To keep track of visited vertices
@ -756,10 +787,18 @@ class Graph:
if current_id == vertex_id: if current_id == vertex_id:
# We should add to visited all the vertices that are successors of the current vertex # We should add to visited all the vertices that are successors of the current vertex
# and their successors and so on # and their successors and so on
# if the vertex is a start, it means we are starting from the beginning
# and getting successors
for successor in current_vertex.successors: for successor in current_vertex.successors:
if is_start:
stack.append(successor.id)
else:
excluded.add(successor.id) excluded.add(successor.id)
all_successors = get_successors(successor) all_successors = get_successors(successor)
for successor in all_successors: for successor in all_successors:
if is_start:
stack.append(successor.id)
else:
excluded.add(successor.id) excluded.add(successor.id)
# Filter the original graph's vertices and edges to keep only those in `visited` # Filter the original graph's vertices and edges to keep only those in `visited`
@ -871,12 +910,19 @@ class Graph:
return vertices_layers return vertices_layers
def sort_vertices(self, component_id: Optional[str] = None) -> List[List[str]]: def sort_vertices(
self,
stop_component_id: Optional[str] = None,
start_component_id: Optional[str] = None,
) -> List[str]:
"""Sorts the vertices in the graph.""" """Sorts the vertices in the graph."""
self.mark_all_vertices("ACTIVE") self.mark_all_vertices("ACTIVE")
if component_id: if stop_component_id:
vertices = self.sort_up_to_vertex(component_id) self.stop_vertex = stop_component_id
vertices_layers = self.layered_topological_sort(vertices) vertices = self.sort_up_to_vertex(stop_component_id)
elif start_component_id:
vertices = self.sort_up_to_vertex(start_component_id, is_start=True)
else: else:
vertices = self.vertices vertices = self.vertices
# without component_id we are probably running in the chat # without component_id we are probably running in the chat
@ -886,10 +932,23 @@ class Graph:
vertices, filter_graphs=True vertices, filter_graphs=True
) )
vertices_layers = self.sort_by_avg_build_time(vertices_layers) vertices_layers = self.sort_by_avg_build_time(vertices_layers)
vertices_layers = self.sort_chat_inputs_first(vertices_layers) # vertices_layers = self.sort_chat_inputs_first(vertices_layers)
self.increment_run_count() self.increment_run_count()
self._sorted_vertices_layers = vertices_layers first_layer = vertices_layers[0]
return vertices_layers # save the only the rest
self.vertices_layers = vertices_layers[1:]
self.vertices_to_run = {
vertex for vertex in chain.from_iterable(vertices_layers)
}
# Return just the first layer
return first_layer
def should_run_vertex(self, vertex_id: str) -> bool:
"""Returns whether a component should be run."""
should_run = vertex_id in self.vertices_to_run
if should_run:
self.vertices_to_run.remove(vertex_id)
return should_run
def sort_interface_components_first( def sort_interface_components_first(
self, vertices_layers: List[List[str]] self, vertices_layers: List[List[str]]

View file

@ -47,7 +47,10 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()}, **{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()}, **{
t: types.CustomComponentVertex
for t in custom_component_creator.to_list()
},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS}, **{t: types.ChatVertex for t in CHAT_COMPONENTS},
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS}, **{t: types.RoutingVertex for t in ROUTING_COMPONENTS},

View file

@ -2,13 +2,26 @@ import ast
import inspect import inspect
import types import types
from enum import Enum from enum import Enum
from typing import (TYPE_CHECKING, Any, AsyncIterator, Callable, Coroutine, from typing import (
Dict, Iterator, List, Optional) TYPE_CHECKING,
Any,
AsyncIterator,
Callable,
Coroutine,
Dict,
Iterator,
List,
Optional,
)
from loguru import logger from loguru import logger
from langflow.graph.schema import (INPUT_COMPONENTS, OUTPUT_COMPONENTS, from langflow.graph.schema import (
InterfaceComponentTypes, ResultData) INPUT_COMPONENTS,
OUTPUT_COMPONENTS,
InterfaceComponentTypes,
ResultData,
)
from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.utils import UnbuiltObject, UnbuiltResult
from langflow.graph.vertex.utils import generate_result from langflow.graph.vertex.utils import generate_result
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
@ -152,6 +165,10 @@ class Vertex:
def successors(self) -> List["Vertex"]: def successors(self) -> List["Vertex"]:
return self.graph.get_successors(self) return self.graph.get_successors(self)
@property
def successors_ids(self) -> List[str]:
return self.graph.successor_map.get(self.id, [])
def __getstate__(self): def __getstate__(self):
return { return {
"_data": self._data, "_data": self._data,
@ -594,9 +611,6 @@ class Vertex:
raise ValueError( raise ValueError(
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead." f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
) )
raise ValueError(
f"{self.display_name}: You are trying to stream to a non-streamable component."
)
def _reset(self, params_update: Optional[Dict[str, Any]] = None): def _reset(self, params_update: Optional[Dict[str, Any]] = None):
self._built = False self._built = False
@ -606,6 +620,9 @@ class Vertex:
self.steps_ran = [] self.steps_ran = []
self._build_params() self._build_params()
def _is_chat_input(self):
return False
def build_inactive(self): def build_inactive(self):
# Just set the results to None # Just set the results to None
self._built = True self._built = True
@ -632,7 +649,7 @@ class Vertex:
return await self.get_requester_result(requester) return await self.get_requester_result(requester)
self._reset() self._reset()
if self.is_input and inputs is not None: if self._is_chat_input() and inputs is not None:
self.update_raw_params(inputs) self.update_raw_params(inputs)
# Run steps # Run steps

View file

@ -6,7 +6,7 @@ import yaml
from langchain_core.messages import AIMessage from langchain_core.messages import AIMessage
from loguru import logger from loguru import logger
from langflow.graph.schema import INPUT_FIELD_NAME from langflow.graph.schema import INPUT_FIELD_NAME, InterfaceComponentTypes
from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
@ -455,18 +455,30 @@ class ChatVertex(Vertex):
async for _ in self.stream(): async for _ in self.stream():
pass pass
def _is_chat_input(self):
return self.vertex_type == InterfaceComponentTypes.ChatInput and self.is_input
class RoutingVertex(Vertex): class RoutingVertex(Vertex):
def __init__(self, data: Dict, graph): def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="custom_components") super().__init__(data, graph=graph, base_type="custom_components")
self.use_result = True self.use_result = True
self.steps = [self._build, self._run] self.steps = [self._build]
def _built_object_repr(self): def _built_object_repr(self):
if self.artifacts and "repr" in self.artifacts: if self.artifacts and "repr" in self.artifacts:
return self.artifacts["repr"] or super()._built_object_repr() return self.artifacts["repr"] or super()._built_object_repr()
return super()._built_object_repr() return super()._built_object_repr()
@property
def successors_ids(self):
if isinstance(self._built_object, bool):
ids = super().successors_ids
if self._built_object:
return ids
return []
raise ValueError("RoutingVertex should return a boolean value.")
def _run(self, *args, **kwargs): def _run(self, *args, **kwargs):
if self._built_object: if self._built_object:
condition = self._built_object.get("condition") condition = self._built_object.get("condition")

View file

@ -17,10 +17,10 @@ import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import { import {
INPUT_HANDLER_HOVER,
LANGFLOW_SUPPORTED_TYPES, LANGFLOW_SUPPORTED_TYPES,
OUTPUT_HANDLER_HOVER,
TOOLTIP_EMPTY, TOOLTIP_EMPTY,
inputHandleHover,
outputHandleHover,
} from "../../../../constants/constants"; } from "../../../../constants/constants";
import { postCustomComponentUpdate } from "../../../../controllers/API"; import { postCustomComponentUpdate } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
@ -182,7 +182,7 @@ export default function ParameterComponent({
return ( return (
<div key={index}> <div key={index}>
{index === 0 && ( {index === 0 && (
<span>{left ? inputHandleHover : outputHandleHover}</span> <span>{left ? INPUT_HANDLER_HOVER : OUTPUT_HANDLER_HOVER}</span>
)} )}
<span <span
key={index} key={index}
@ -304,7 +304,10 @@ export default function ParameterComponent({
ref={ref} ref={ref}
className={ className={
"relative mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2" + "relative mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2" +
(name === "code" ? " hidden " : "") ((name === "code" && type === "code") ||
(name.includes("code") && proxy)
? " hidden "
: "")
} }
> >
<> <>

View file

@ -9,9 +9,9 @@ import Loading from "../../components/ui/loading";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import Xmark from "../../components/ui/xmark"; import Xmark from "../../components/ui/xmark";
import { import {
STATUS_BUILD,
STATUS_BUILDING,
priorityFields, priorityFields,
statusBuild,
statusBuilding,
} from "../../constants/constants"; } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums"; import { BuildStatus } from "../../constants/enums";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
@ -49,7 +49,12 @@ export default function GenericNode({
const [nodeDescription, setNodeDescription] = useState( const [nodeDescription, setNodeDescription] = useState(
data.node?.description! data.node?.description!
); );
const buildStatus = useFlowStore((state) => state.flowBuildStatus[data.id]); const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status
);
const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp
);
const [validationStatus, setValidationStatus] = const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null); useState<validationStatusType | null>(null);
const [handles, setHandles] = useState<number>(0); const [handles, setHandles] = useState<number>(0);
@ -332,8 +337,8 @@ export default function GenericNode({
/> />
</div> </div>
) : ( ) : (
<div className="group flex items-center gap-2.5"> <div className="group flex items-start gap-1.5">
<ShadTooltip content={data.id}> <ShadTooltip content={data.node?.display_name}>
<div <div
onDoubleClick={(event) => { onDoubleClick={(event) => {
if (nameEditable) { if (nameEditable) {
@ -359,8 +364,8 @@ export default function GenericNode({
}} }}
> >
<IconComponent <IconComponent
name="Pencil" name="PencilLine"
className="hidden h-4 w-4 animate-pulse text-status-blue group-hover:block" className="hidden h-3 w-3 text-status-blue group-hover:block"
/> />
</div> </div>
)} )}
@ -390,12 +395,33 @@ export default function GenericNode({
})} })}
data={data} data={data}
color={ color={
nodeColors[ data.node?.template[templateField].input_types &&
types[data.node?.template[templateField].type!] data.node?.template[templateField].input_types!
.length > 0
? nodeColors[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ?? ] ??
nodeColors[ nodeColors[
types[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
]
]
: nodeColors[
data.node?.template[templateField].type! data.node?.template[templateField].type!
] ?? ] ??
nodeColors[
types[
data.node?.template[templateField].type!
]
] ??
nodeColors.unknown nodeColors.unknown
} }
title={getFieldTitle( title={getFieldTitle(
@ -458,23 +484,14 @@ export default function GenericNode({
)} )}
</div> </div>
{showNode && ( {showNode && (
<Button <Button variant="secondary" className={"group h-9 px-1.5"}>
variant="secondary"
className={"group h-9 px-1.5"}
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ nodeId: data.id });
}}
>
<div> <div>
<ShadTooltip <ShadTooltip
content={ content={
buildStatus === BuildStatus.BUILDING ? ( buildStatus === BuildStatus.BUILDING ? (
<span> {statusBuilding} </span> <span> {STATUS_BUILDING} </span>
) : !validationStatus ? ( ) : !validationStatus ? (
<span className="flex">{statusBuild}</span> <span className="flex">{STATUS_BUILD}</span>
) : ( ) : (
<div className="max-h-96 overflow-auto"> <div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string" {typeof validationStatus.params === "string"
@ -489,7 +506,15 @@ export default function GenericNode({
} }
side="bottom" side="bottom"
> >
<div className="generic-node-status-position flex items-center justify-center"> <div
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
className="generic-node-status-position flex items-center justify-center"
>
{renderIconStatus(buildStatus, validationStatus)} {renderIconStatus(buildStatus, validationStatus)}
</div> </div>
</ShadTooltip> </ShadTooltip>
@ -607,13 +632,18 @@ export default function GenericNode({
data.node?.template[templateField].input_types! data.node?.template[templateField].input_types!
.length > 0 .length > 0
? nodeColors[ ? nodeColors[
data.node?.template[templateField].input_types![
data.node?.template[templateField] data.node?.template[templateField]
.input_types![0] .input_types!.length - 1
]
] ?? ] ??
nodeColors[ nodeColors[
types[ types[
data.node?.template[templateField] data.node?.template[templateField]
.input_types![0] .input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ]
] ]
: nodeColors[ : nodeColors[
@ -695,6 +725,15 @@ export default function GenericNode({
showNode={showNode} showNode={showNode}
/> />
)} )}
<div>
{lastRunTime && (
<div className="flex justify-center text-muted-foreground">
{lastRunTime.split("\n").map((line, index) => (
<div key={index}>{line}</div>
))}
</div>
)}
</div>
</> </>
</div> </div>
)} )}

View file

@ -6,7 +6,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "../../components/ui/popover"; } from "../../components/ui/popover";
import { zeroNotifications } from "../../constants/constants"; import { ZERO_NOTIFICATIONS } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { AlertDropdownType } from "../../types/alerts"; import { AlertDropdownType } from "../../types/alerts";
import SingleAlert from "./components/singleAlertComponent"; import SingleAlert from "./components/singleAlertComponent";
@ -70,7 +70,7 @@ export default function AlertDropdown({
)) ))
) : ( ) : (
<div className="flex h-full w-full items-center justify-center pb-16 text-ring"> <div className="flex h-full w-full items-center justify-center pb-16 text-ring">
{zeroNotifications} {ZERO_NOTIFICATIONS}
</div> </div>
)} )}
</div> </div>

View file

@ -17,7 +17,7 @@ export default function IOOutputView({
case "TextOutput": case "TextOutput":
return ( return (
<Textarea <Textarea
className="w-full h-full custom-scroll" className="w-full custom-scroll"
placeholder={"Empty"} placeholder={"Empty"}
// update to real value on flowPool // update to real value on flowPool
value={ value={
@ -31,7 +31,7 @@ export default function IOOutputView({
default: default:
return ( return (
<Textarea <Textarea
className="w-full h-full custom-scroll" className="w-full custom-scroll"
placeholder={"Enter text..."} placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]} value={node.data.node!.template["input_value"]}
onChange={(e) => { onChange={(e) => {

View file

@ -1,12 +1,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
CHAT_FORM_DIALOG_SUBTITLE, CHAT_FORM_DIALOG_SUBTITLE,
outputsModalTitle, OUTPUTS_MODAL_TITLE,
textInputModalTitle, TEXT_INPUT_MODAL_TITLE,
} from "../../constants/constants"; } from "../../constants/constants";
import BaseModal from "../../modals/baseModal"; import BaseModal from "../../modals/baseModal";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { NodeType } from "../../types/flow";
import { updateVerticesOrder } from "../../utils/buildUtils"; import { updateVerticesOrder } from "../../utils/buildUtils";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent"; import AccordionComponent from "../AccordionComponent";
@ -51,6 +52,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
const [chatValue, setChatValue] = useState(""); const [chatValue, setChatValue] = useState("");
const isBuilding = useFlowStore((state) => state.isBuilding); const isBuilding = useFlowStore((state) => state.isBuilding);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setNode = useFlowStore((state) => state.setNode);
async function updateVertices() { async function updateVertices() {
return updateVerticesOrder(currentFlow!.id, null); return updateVerticesOrder(currentFlow!.id, null);
@ -68,12 +70,22 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
setLockChat(true); setLockChat(true);
setChatValue(""); setChatValue("");
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
await buildFlow({ input_value: chatValue }).catch((err) => { await buildFlow({
input_value: chatValue,
startNodeId: chatInput?.id,
}).catch((err) => {
console.error(err); console.error(err);
setLockChat(false); setLockChat(false);
}); });
} }
setLockChat(false); setLockChat(false);
if (chatInput) {
setNode(chatInput.id, (node: NodeType) => {
const newNode = { ...node };
newNode.data.node!.template["input_value"].value = chatValue;
return newNode;
});
}
} }
useEffect(() => { useEffect(() => {
@ -135,7 +147,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
> >
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold"> <div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
<IconComponent className="h-4 w-4" name={"Type"} /> <IconComponent className="h-4 w-4" name={"Type"} />
{textInputModalTitle} {TEXT_INPUT_MODAL_TITLE}
</div> </div>
{nodes {nodes
.filter((node) => .filter((node) =>
@ -196,7 +208,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
> >
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold"> <div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
<IconComponent className="h-4 w-4" name={"FileType2"} /> <IconComponent className="h-4 w-4" name={"FileType2"} />
{outputsModalTitle} {OUTPUTS_MODAL_TITLE}
</div> </div>
{nodes {nodes
.filter((node) => .filter((node) =>
@ -220,7 +232,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
> >
<div> <div>
<Badge variant="gray" size="md"> <Badge variant="gray" size="md">
{output.displayName} {node.data.node.display_name}
</Badge> </Badge>
</div> </div>
</ShadTooltip> </ShadTooltip>
@ -263,7 +275,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
)} )}
{haveChat ? ( {haveChat ? (
<div className="flex h-full w-full"> <div className="flex h-full min-w-96 flex-grow">
{selectedViewField && ( {selectedViewField && (
<div <div
className={cn( className={cn(

View file

@ -14,7 +14,8 @@ export default function CodeAreaComponent({
setNodeClass, setNodeClass,
id = "", id = "",
readonly = false, readonly = false,
openModal, open,
setOpen,
}: CodeAreaComponentType) { }: CodeAreaComponentType) {
const [myValue, setMyValue] = useState( const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value) typeof value == "string" ? value : JSON.stringify(value)
@ -33,7 +34,8 @@ export default function CodeAreaComponent({
return ( return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}> <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<CodeAreaModal <CodeAreaModal
openModal={openModal} open={open}
setOpen={setOpen}
readonly={readonly} readonly={readonly}
dynamic={dynamic} dynamic={dynamic}
value={myValue} value={myValue}

View file

@ -193,9 +193,9 @@ export default function CodeTabsComponent({
></div> ></div>
)} )}
<SyntaxHighlighter <SyntaxHighlighter
className="mt-0 h-full w-full overflow-auto custom-scroll" language={tab.language}
language={tab.mode}
style={oneDark} style={oneDark}
className="mt-0 h-full overflow-auto custom-scroll rounded-sm text-left"
> >
{tab.code} {tab.code}
</SyntaxHighlighter> </SyntaxHighlighter>

View file

@ -9,7 +9,7 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Node } from "reactflow"; import { Node } from "reactflow";
import { savedHover } from "../../../../constants/constants"; import { SAVED_HOVER } from "../../../../constants/constants";
import FlowSettingsModal from "../../../../modals/flowSettingsModal"; import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
@ -128,7 +128,7 @@ export const MenuBar = ({
</div> </div>
<ShadTooltip <ShadTooltip
content={ content={
savedHover + SAVED_HOVER +
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", { new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",

View file

@ -2,8 +2,8 @@ import { useEffect, useState } from "react";
import IconComponent from "../../../components/genericIconComponent"; import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea"; import { Textarea } from "../../../components/ui/textarea";
import { import {
chatInputPlaceholder, CHAT_INPUT_PLACEHOLDER,
chatInputPlaceholderSend, CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants"; } from "../../../constants/constants";
import useFlowsManagerStore from "../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { chatInputType } from "../../../types/components"; import { chatInputType } from "../../../types/components";
@ -84,7 +84,7 @@ export default function ChatInput({
"form-modal-lockchat" "form-modal-lockchat"
)} )}
placeholder={ placeholder={
noInput ? chatInputPlaceholder : chatInputPlaceholderSend noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND
} }
/> />
<div className="form-modal-send-icon-position"> <div className="form-modal-send-icon-position">

View file

@ -134,7 +134,7 @@ export default function ChatMessage({
)} )}
</div> </div>
{!chat.isSend ? ( {!chat.isSend ? (
<div className="form-modal-chat-text-position"> <div className="form-modal-chat-text-position flex-grow min-w-96">
<div className="form-modal-chat-text"> <div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && ( {hidden && chat.thought && chat.thought !== "" && (
<div <div
@ -155,9 +155,9 @@ export default function ChatMessage({
/> />
)} )}
{chat.thought && chat.thought !== "" && !hidden && <br></br>} {chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full"> <div className="w-full flex flex-col">
<div className="w-full dark:text-white"> <div className="w-full flex flex-col dark:text-white">
<div className="w-full"> <div className="w-full flex flex-col">
{useMemo( {useMemo(
() => () =>
chatMessage === "" && lockChat ? ( chatMessage === "" && lockChat ? (
@ -169,7 +169,7 @@ export default function ChatMessage({
<Markdown <Markdown
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]} rehypePlugins={[rehypeMathjax]}
className="markdown prose min-w-full text-primary word-break-break-word className="markdown flex flex-col prose text-primary word-break-break-word
dark:prose-invert" dark:prose-invert"
components={{ components={{
pre({ node, ...props }) { pre({ node, ...props }) {

View file

@ -2,8 +2,8 @@ import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants"; import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
import { import {
chatFirstInitialText, CHAT_FIRST_INITIAL_TEXT,
chatSecondInitialText, CHAT_SECOND_INITIAL_TEXT,
} from "../../constants/constants"; } from "../../constants/constants";
import { deleteFlowPool } from "../../controllers/API"; import { deleteFlowPool } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
@ -182,14 +182,14 @@ export default function NewChatView({
<br /> <br />
<div className="langflow-chat-desc"> <div className="langflow-chat-desc">
<span className="langflow-chat-desc-span"> <span className="langflow-chat-desc-span">
{chatFirstInitialText}{" "} {CHAT_FIRST_INITIAL_TEXT}{" "}
<span> <span>
<IconComponent <IconComponent
name="MessageSquare" name="MessageSquare"
className="mx-1 inline h-5 w-5 animate-bounce " className="mx-1 inline h-5 w-5 animate-bounce "
/> />
</span>{" "} </span>{" "}
{chatSecondInitialText} {CHAT_SECOND_INITIAL_TEXT}
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { editTextModalTitle } from "../../constants/constants"; import { EDIT_TEXT_MODAL_TITLE } from "../../constants/constants";
import { TypeModal } from "../../constants/enums"; import { TypeModal } from "../../constants/enums";
import GenericModal from "../../modals/genericModal"; import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components"; import { TextAreaComponentType } from "../../types/components";
@ -38,7 +38,7 @@ export default function TextAreaComponent({
<GenericModal <GenericModal
type={TypeModal.TEXT} type={TypeModal.TEXT}
buttonText="Finish Editing" buttonText="Finish Editing"
modalTitle={editTextModalTitle} modalTitle={EDIT_TEXT_MODAL_TITLE}
value={value} value={value}
setValue={(value: string) => { setValue={(value: string) => {
onChange(value); onChange(value);

View file

@ -43,7 +43,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full", "flex flex-col fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className className
)} )}
{...props} {...props}

View file

@ -56,3 +56,6 @@ export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!"; export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`; export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!"; export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
// Generic Node

View file

@ -691,38 +691,39 @@ export const priorityFields = new Set(["code", "template"]);
export const INPUT_TYPES = new Set(["ChatInput", "TextInput"]); export const INPUT_TYPES = new Set(["ChatInput", "TextInput"]);
export const OUTPUT_TYPES = new Set(["ChatOutput", "TextOutput"]); export const OUTPUT_TYPES = new Set(["ChatOutput", "TextOutput"]);
export const chatFirstInitialText = export const CHAT_FIRST_INITIAL_TEXT =
"Start a conversation and click the agent's thoughts"; "Start a conversation and click the agent's thoughts";
export const chatSecondInitialText = "to inspect the chaining process."; export const CHAT_SECOND_INITIAL_TEXT = "to inspect the chaining process.";
export const zeroNotifications = "No new notifications"; export const ZERO_NOTIFICATIONS = "No new notifications";
export const successBuild = "Built sucessfully ✨"; export const SUCCESS_BUILD = "Built sucessfully ✨";
export const alertSaveWApi = export const ALERT_SAVE_WITH_API =
"Caution: Uncheck this box only removes API keys from fields specifically designated for API keys."; "Caution: Uncheck this box only removes API keys from fields specifically designated for API keys.";
export const saveWApiCheckbox = "Save with my API keys"; export const SAVE_WITH_API_CHECKBOX = "Save with my API keys";
export const editTextModalTitle = "Edit Text"; export const EDIT_TEXT_MODAL_TITLE = "Edit Text";
export const editTextPlaceholder = "Type message here."; export const EDIT_TEXT_PLACEHOLDER = "Type message here.";
export const inputHandleHover = "Avaliable input components:"; export const INPUT_HANDLER_HOVER = "Avaliable input components:";
export const outputHandleHover = "Avaliable output components:"; export const OUTPUT_HANDLER_HOVER = "Avaliable output components:";
export const textInputModalTitle = "Text Inputs"; export const TEXT_INPUT_MODAL_TITLE = "Text Inputs";
export const outputsModalTitle = "Text Outputs"; export const OUTPUTS_MODAL_TITLE = "Text Outputs";
export const langflowChatTitle = "Langflow Chat"; export const LANGFLOW_CHAT_TITLE = "Langflow Chat";
export const chatInputPlaceholder = export const CHAT_INPUT_PLACEHOLDER =
"No chat input variables found. Click to run your flow."; "No chat input variables found. Click to run your flow.";
export const chatInputPlaceholderSend = "Send a message..."; export const CHAT_INPUT_PLACEHOLDER_SEND = "Send a message...";
export const editCodeTitle = "Edit Code"; export const EDIT_CODE_TITLE = "Edit Code";
export const myCollectionDesc = export const MY_COLLECTION_DESC =
"Manage your personal projects. Download and upload entire collections."; "Manage your personal projects. Download and upload entire collections.";
export const storeDesc = "Explore community-shared flows and components."; export const STORE_DESC = "Explore community-shared flows and components.";
export const storeTitle = "Langflow Store"; export const STORE_TITLE = "Langflow Store";
export const noApi = "You don't have an API key. "; export const NO_API_KEY = "You don't have an API key. ";
export const insertApi = "Insert your Langflow API key."; export const INSERT_API_KEY = "Insert your Langflow API key.";
export const invalidApi = "Your API key is not valid. "; export const INVALID_API_KEY = "Your API key is not valid. ";
export const createApi = `Don’t have an API key? Sign up at`; export const CREATE_API_KEY = `Don’t have an API key? Sign up at`;
export const statusBuild = "Build to validate status."; export const STATUS_BUILD = "Build to validate status.";
export const statusBuilding = "Building..."; export const STATUS_BUILDING = "Building...";
export const savedHover = "Last saved at "; export const SAVED_HOVER = "Last saved at ";
export const RUN_TIMESTAMP_PREFIX = "Last Run: ";

View file

@ -856,13 +856,16 @@ export async function requestLogout() {
export async function getVerticesOrder( export async function getVerticesOrder(
flowId: string, flowId: string,
nodeId?: string | null startNodeId?: string | null,
stopNodeId?: string | null
): Promise<AxiosResponse<VerticesOrderTypeAPI>> { ): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
// nodeId is optional and is a query parameter // nodeId is optional and is a query parameter
// if nodeId is not provided, the API will return all vertices // if nodeId is not provided, the API will return all vertices
const config = {}; const config = {};
if (nodeId) { if (stopNodeId) {
config["params"] = { component_id: nodeId }; config["params"] = { stop_component_id: stopNodeId };
} else if (startNodeId) {
config["params"] = { start_component_id: startNodeId };
} }
return await api.get(`${BASE_URL_API}build/${flowId}/vertices`, config); return await api.get(`${BASE_URL_API}build/${flowId}/vertices`, config);
} }
@ -872,6 +875,7 @@ export async function postBuildVertex(
vertexId: string, vertexId: string,
input_value: string input_value: string
): Promise<AxiosResponse<VertexBuildTypeAPI>> { ): Promise<AxiosResponse<VertexBuildTypeAPI>> {
// input_value is optional and is a query parameter
return await api.post( return await api.post(
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`, `${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
input_value ? { inputs: { input_value: input_value } } : undefined input_value ? { inputs: { input_value: input_value } } : undefined

View file

@ -165,7 +165,8 @@ const EditNodeModal = forwardRef(
) )
) ?? false; ) ?? false;
return ( return (
<TableRow key={index} className="h-10"> <TableRow key={index} className={"h-10 " + ((templateParam==="code" && myData.node?.template[templateParam].type==="code") || (templateParam.includes("code") && myData.node?.template[templateParam].proxy) ? " hidden " : "")
}>
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3"> <TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
<ShadTooltip <ShadTooltip
content={ content={

View file

@ -8,10 +8,10 @@ import {
API_SUCCESS_ALERT, API_SUCCESS_ALERT,
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { import {
createApi, CREATE_API_KEY,
insertApi, INSERT_API_KEY,
invalidApi, INVALID_API_KEY,
noApi, NO_API_KEY,
} from "../../constants/constants"; } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { addApiKeyStore } from "../../controllers/API"; import { addApiKeyStore } from "../../controllers/API";
@ -68,8 +68,11 @@ export default function StoreApiKeyModal({
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger> <BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header <BaseModal.Header
description={ description={
(hasApiKey && !validApiKey ? invalidApi : !hasApiKey ? noApi : "") + (hasApiKey && !validApiKey
insertApi ? INVALID_API_KEY
: !hasApiKey
? NO_API_KEY
: "") + INSERT_API_KEY
} }
> >
<span className="pr-2">API Key</span> <span className="pr-2">API Key</span>
@ -104,7 +107,7 @@ export default function StoreApiKeyModal({
</div> </div>
<div className="flex items-end justify-between"> <div className="flex items-end justify-between">
<span className="pr-1 text-xs text-muted-foreground"> <span className="pr-1 text-xs text-muted-foreground">
{createApi}{" "} {CREATE_API_KEY}{" "}
<a <a
className="text-high-indigo underline" className="text-high-indigo underline"
href="https://langflow.store/" href="https://langflow.store/"

View file

@ -126,7 +126,7 @@ function BaseModal({
minWidth = "min-w-[60vw]"; minWidth = "min-w-[60vw]";
break; break;
case "large": case "large":
minWidth = "min-w-[80vw]"; minWidth = "min-w-[85vw]";
height = "h-[80vh]"; height = "h-[80vh]";
break; break;
case "large-thin": case "large-thin":

View file

@ -18,7 +18,7 @@ import {
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { import {
CODE_PROMPT_DIALOG_SUBTITLE, CODE_PROMPT_DIALOG_SUBTITLE,
editCodeTitle, EDIT_CODE_TITLE,
} from "../../constants/constants"; } from "../../constants/constants";
import { postCustomComponent, postValidateCode } from "../../controllers/API"; import { postCustomComponent, postValidateCode } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
@ -35,9 +35,14 @@ export default function CodeAreaModal({
children, children,
dynamic, dynamic,
readonly = false, readonly = false,
openModal, open: myOpen,
setOpen: mySetOpen,
}: codeAreaModalPropsType): JSX.Element { }: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value); const [code, setCode] = useState(value);
const [open, setOpen] =
mySetOpen !== undefined && myOpen !== undefined
? [myOpen, mySetOpen]
: useState(false);
const dark = useDarkStore((state) => state.dark); const dark = useDarkStore((state) => state.dark);
const unselectAll = useFlowStore((state) => state.unselectAll); const unselectAll = useFlowStore((state) => state.unselectAll);
@ -56,10 +61,6 @@ export default function CodeAreaModal({
} }
}, []); }, []);
useEffect(() => {
if (openModal) setOpen(true);
}, [openModal]);
function processNonDynamicField() { function processNonDynamicField() {
postValidateCode(code) postValidateCode(code)
.then((apiReturn) => { .then((apiReturn) => {
@ -143,8 +144,6 @@ export default function CodeAreaModal({
}; };
}, [error, setHeight]); }, [error, setHeight]);
const [open, setOpen] = useState(false);
useEffect(() => { useEffect(() => {
setCode(value); setCode(value);
}, [value, open]); }, [value, open]);
@ -153,7 +152,7 @@ export default function CodeAreaModal({
<BaseModal open={open} setOpen={setOpen}> <BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}> <BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
<span className="pr-2"> {editCodeTitle} </span> <span className="pr-2"> {EDIT_CODE_TITLE} </span>
<IconComponent <IconComponent
name="prompts" name="prompts"
className="h-6 w-6 pl-1 text-primary " className="h-6 w-6 pl-1 text-primary "

View file

@ -5,9 +5,9 @@ import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox"; import { Checkbox } from "../../components/ui/checkbox";
import { API_WARNING_NOTICE_ALERT } from "../../constants/alerts_constants"; import { API_WARNING_NOTICE_ALERT } from "../../constants/alerts_constants";
import { import {
ALERT_SAVE_WITH_API,
EXPORT_DIALOG_SUBTITLE, EXPORT_DIALOG_SUBTITLE,
alertSaveWApi, SAVE_WITH_API_CHECKBOX,
saveWApiCheckbox,
} from "../../constants/constants"; } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
@ -56,10 +56,12 @@ const ExportModal = forwardRef(
}} }}
/> />
<label htmlFor="terms" className="export-modal-save-api text-sm "> <label htmlFor="terms" className="export-modal-save-api text-sm ">
{saveWApiCheckbox} {SAVE_WITH_API_CHECKBOX}
</label> </label>
</div> </div>
<span className=" text-xs text-destructive ">{alertSaveWApi}</span> <span className=" text-xs text-destructive ">
{ALERT_SAVE_WITH_API}
</span>
</BaseModal.Content> </BaseModal.Content>
<BaseModal.Footer> <BaseModal.Footer>

View file

@ -2,8 +2,8 @@ import { useEffect } from "react";
import IconComponent from "../../../components/genericIconComponent"; import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea"; import { Textarea } from "../../../components/ui/textarea";
import { import {
chatInputPlaceholder, CHAT_INPUT_PLACEHOLDER,
chatInputPlaceholderSend, CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants"; } from "../../../constants/constants";
import { chatInputType } from "../../../types/components"; import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils"; import { classNames } from "../../../utils/utils";
@ -55,7 +55,7 @@ export default function ChatInput({
? "Thinking..." ? "Thinking..."
: typeof chatValue === "object" && : typeof chatValue === "object" &&
Object.keys(chatValue)?.length === 0 Object.keys(chatValue)?.length === 0
? chatInputPlaceholder ? CHAT_INPUT_PLACEHOLDER
: chatValue : chatValue
} }
onChange={(event): void => { onChange={(event): void => {
@ -70,7 +70,9 @@ export default function ChatInput({
"form-modal-lockchat" "form-modal-lockchat"
)} )}
placeholder={noInput ? chatInputPlaceholder : chatInputPlaceholderSend} placeholder={
noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND
}
/> />
<div className="form-modal-send-icon-position"> <div className="form-modal-send-icon-position">
<button <button

View file

@ -26,10 +26,10 @@ import {
MSG_ERROR_ALERT, MSG_ERROR_ALERT,
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { import {
CHAT_FIRST_INITIAL_TEXT,
CHAT_FORM_DIALOG_SUBTITLE, CHAT_FORM_DIALOG_SUBTITLE,
chatFirstInitialText, CHAT_SECOND_INITIAL_TEXT,
chatSecondInitialText, LANGFLOW_CHAT_TITLE,
langflowChatTitle,
} from "../../constants/constants"; } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { getBuildStatus } from "../../controllers/API"; import { getBuildStatus } from "../../controllers/API";
@ -594,20 +594,20 @@ export default function FormModal({
<span> <span>
👋{" "} 👋{" "}
<span className="langflow-chat-span"> <span className="langflow-chat-span">
{langflowChatTitle} {LANGFLOW_CHAT_TITLE}
</span> </span>
</span> </span>
<br /> <br />
<div className="langflow-chat-desc"> <div className="langflow-chat-desc">
<span className="langflow-chat-desc-span"> <span className="langflow-chat-desc-span">
{chatFirstInitialText}{" "} {CHAT_FIRST_INITIAL_TEXT}{" "}
<span> <span>
<IconComponent <IconComponent
name="MessageSquare" name="MessageSquare"
className="mx-1 inline h-5 w-5 animate-bounce " className="mx-1 inline h-5 w-5 animate-bounce "
/> />
</span>{" "} </span>{" "}
{chatSecondInitialText} {CHAT_SECOND_INITIAL_TEXT}
</span> </span>
</div> </div>
</div> </div>

View file

@ -12,11 +12,11 @@ import {
TEMP_NOTICE_ALERT, TEMP_NOTICE_ALERT,
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { import {
EDIT_TEXT_PLACEHOLDER,
INVALID_CHARACTERS, INVALID_CHARACTERS,
MAX_WORDS_HIGHLIGHT, MAX_WORDS_HIGHLIGHT,
PROMPT_DIALOG_SUBTITLE, PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE,
editTextPlaceholder,
regexHighlight, regexHighlight,
} from "../../constants/constants"; } from "../../constants/constants";
import { TypeModal } from "../../constants/enums"; import { TypeModal } from "../../constants/enums";
@ -227,7 +227,7 @@ export default function GenericModal({
setInputValue(event.target.value); setInputValue(event.target.value);
checkVariables(event.target.value); checkVariables(event.target.value);
}} }}
placeholder={editTextPlaceholder} placeholder={EDIT_TEXT_PLACEHOLDER}
onKeyDown={(e) => { onKeyDown={(e) => {
handleKeyDown(e, inputValue, ""); handleKeyDown(e, inputValue, "");
}} }}
@ -249,7 +249,7 @@ export default function GenericModal({
onChange={(event) => { onChange={(event) => {
setInputValue(event.target.value); setInputValue(event.target.value);
}} }}
placeholder={editTextPlaceholder} placeholder={EDIT_TEXT_PLACEHOLDER}
onKeyDown={(e) => { onKeyDown={(e) => {
handleKeyDown(e, value, ""); handleKeyDown(e, value, "");
}} }}

View file

@ -219,23 +219,6 @@ export default function NodeToolbarComponent({
}} }}
data-testid="code-button-modal" data-testid="code-button-modal"
> >
<div className="hidden">
<CodeAreaComponent
openModal={openModal}
readonly={
data.node?.flow && data.node.template[name].dynamic
? true
: false
}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={handleNodeClass}
nodeClass={data.node}
disabled={false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-node-toolbar-" + name}
/>
</div>
<IconComponent name="TerminalSquare" className="h-4 w-4" /> <IconComponent name="TerminalSquare" className="h-4 w-4" />
</button> </button>
</ShadTooltip> </ShadTooltip>
@ -492,6 +475,26 @@ export default function NodeToolbarComponent({
is_component={true} is_component={true}
component={flowComponent!} component={flowComponent!}
/> />
{hasCode && (
<div className="hidden">
<CodeAreaComponent
open={openModal}
setOpen={setOpenModal}
readonly={
data.node?.flow && data.node.template[name].dynamic
? true
: false
}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={handleNodeClass}
nodeClass={data.node}
disabled={false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-node-toolbar-" + name}
/>
</div>
)}
</span> </span>
</div> </div>
</> </>

View file

@ -27,7 +27,7 @@ export default function ComponentsComponent({
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(20);
const [pageIndex, setPageIndex] = useState(1); const [pageIndex, setPageIndex] = useState(1);
const [loadingScreen, setLoadingScreen] = useState(true); const [loadingScreen, setLoadingScreen] = useState(true);
@ -96,7 +96,7 @@ export default function ComponentsComponent({
function resetFilter() { function resetFilter() {
setPageIndex(1); setPageIndex(1);
setPageSize(10); setPageSize(20);
} }
return ( return (

View file

@ -8,8 +8,8 @@ import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants"; import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
import { import {
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER, USER_PROJECTS_HEADER,
myCollectionDesc,
} from "../../constants/constants"; } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
@ -75,7 +75,7 @@ export default function HomePage(): JSX.Element {
return ( return (
<PageLayout <PageLayout
title={USER_PROJECTS_HEADER} title={USER_PROJECTS_HEADER}
description={myCollectionDesc} description={MY_COLLECTION_DESC}
button={ button={
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button

View file

@ -26,7 +26,7 @@ import {
INVALID_API_ERROR_ALERT, INVALID_API_ERROR_ALERT,
NOAPI_ERROR_ALERT, NOAPI_ERROR_ALERT,
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { storeDesc, storeTitle } from "../../constants/constants"; import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { getStoreComponents, getStoreTags } from "../../controllers/API"; import { getStoreComponents, getStoreTags } from "../../controllers/API";
import StoreApiKeyModal from "../../modals/StoreApiKeyModal"; import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
@ -174,8 +174,8 @@ export default function StorePage(): JSX.Element {
return ( return (
<PageLayout <PageLayout
betaIcon betaIcon
title={storeTitle} title={STORE_TITLE}
description={storeDesc} description={STORE_DESC}
button={ button={
<> <>
{StoreApiKeyModal && ( {StoreApiKeyModal && (

View file

@ -13,6 +13,7 @@ import {
FLOW_BUILD_SUCCESS_ALERT, FLOW_BUILD_SUCCESS_ALERT,
MISSED_ERROR_ALERT, MISSED_ERROR_ALERT,
} from "../constants/alerts_constants"; } from "../constants/alerts_constants";
import { RUN_TIMESTAMP_PREFIX } from "../constants/constants";
import { BuildStatus } from "../constants/enums"; import { BuildStatus } from "../constants/enums";
import { getFlowPool } from "../controllers/API"; import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api"; import { VertexBuildTypeAPI } from "../types/api";
@ -416,10 +417,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
}, },
buildFlow: async ({ buildFlow: async ({
nodeId, startNodeId,
stopNodeId,
input_value, input_value,
}: { }: {
nodeId?: string; startNodeId?: string;
stopNodeId?: string;
input_value?: string; input_value?: string;
}) => { }) => {
get().setIsBuilding(true); get().setIsBuilding(true);
@ -444,7 +447,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function handleBuildUpdate( function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI, vertexBuildData: VertexBuildTypeAPI,
status: BuildStatus, status: BuildStatus,
buildId: string runId: string
) { ) {
if (vertexBuildData && vertexBuildData.inactivated_vertices) { if (vertexBuildData && vertexBuildData.inactivated_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices); get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
@ -452,20 +455,47 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
if (vertexBuildData && vertexBuildData.activated_layers) { if (vertexBuildData && vertexBuildData.activated_layers) {
get().addToVerticesBuild(vertexBuildData.activated_layers.flat()); get().addToVerticesBuild(vertexBuildData.activated_layers.flat());
} }
if (vertexBuildData.next_vertices_ids) {
// next_vertices_ids is a list of vertices that are going to be built next
// verticesLayers is a list of list of vertices ids, where each list is a layer of vertices
// we want to add a new layer (next_vertices_ids) to the list of layers (verticesLayers)
// and the values of next_vertices_ids to the list of vertices ids (verticesIds)
const newLayers = [
...get().verticesBuild!.verticesLayers,
vertexBuildData.next_vertices_ids,
];
const newIds = [
...get().verticesBuild!.verticesIds,
...vertexBuildData.next_vertices_ids,
];
get().updateVerticesBuild({
verticesIds: newIds,
verticesLayers: newLayers,
runId: runId,
});
get().updateBuildStatus(
vertexBuildData.next_vertices_ids,
BuildStatus.TO_BUILD
);
}
get().addDataToFlowPool( get().addDataToFlowPool(
{ ...vertexBuildData, buildId }, { ...vertexBuildData, buildId: runId },
vertexBuildData.id vertexBuildData.id
); );
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status); useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
} }
await buildVertices({ await buildVertices({
input_value, input_value,
flowId: currentFlow!.id, flowId: currentFlow!.id,
nodeId, startNodeId,
stopNodeId,
onGetOrderSuccess: () => { onGetOrderSuccess: () => {
setNoticeData({ title: "Running components" }); setNoticeData({ title: "Running components" });
}, },
onBuildComplete: () => { onBuildComplete: () => {
const nodeId = startNodeId || stopNodeId;
if (nodeId) { if (nodeId) {
setSuccessData({ setSuccessData({
title: `${ title: `${
@ -482,6 +512,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
onBuildError: (title, list, idList) => { onBuildError: (title, list, idList) => {
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILT); useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILT);
setErrorData({ list, title }); setErrorData({ list, title });
get().setIsBuilding(false);
}, },
onBuildStart: (idList) => { onBuildStart: (idList) => {
console.log("onBuildStart", idList); console.log("onBuildStart", idList);
@ -489,6 +520,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}, },
validateNodes: validateSubgraph, validateNodes: validateSubgraph,
}); });
get().setIsBuilding(false);
get().revertBuiltStatusFromBuilding(); get().revertBuiltStatusFromBuilding();
}, },
getFlow: () => { getFlow: () => {
@ -505,6 +537,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
runId: string; runId: string;
} | null } | null
) => { ) => {
console.log("updateVerticesBuild", vertices);
set({ verticesBuild: vertices }); set({ verticesBuild: vertices });
}, },
verticesBuild: null, verticesBuild: null,
@ -531,17 +564,27 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
}, },
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => { updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
console.log("updateBuildStatus", nodeIdList, status);
const newFlowBuildStatus = { ...get().flowBuildStatus }; const newFlowBuildStatus = { ...get().flowBuildStatus };
nodeIdList.forEach((id) => { nodeIdList.forEach((id) => {
newFlowBuildStatus[id] = status; newFlowBuildStatus[id] = {
status,
};
if (status == BuildStatus.BUILT) {
const timestamp_string = new Date(Date.now()).toLocaleString();
newFlowBuildStatus[
id
].timestamp = `${RUN_TIMESTAMP_PREFIX} ${timestamp_string}`;
}
console.log("updateBuildStatus", newFlowBuildStatus);
}); });
set({ flowBuildStatus: newFlowBuildStatus }); set({ flowBuildStatus: newFlowBuildStatus });
}, },
revertBuiltStatusFromBuilding: () => { revertBuiltStatusFromBuilding: () => {
const newFlowBuildStatus = { ...get().flowBuildStatus }; const newFlowBuildStatus = { ...get().flowBuildStatus };
Object.keys(newFlowBuildStatus).forEach((id) => { Object.keys(newFlowBuildStatus).forEach((id) => {
if (newFlowBuildStatus[id] === BuildStatus.BUILDING) { if (newFlowBuildStatus[id].status === BuildStatus.BUILDING) {
newFlowBuildStatus[id] = BuildStatus.BUILT; newFlowBuildStatus[id].status = BuildStatus.BUILT;
} }
}); });
}, },

View file

@ -845,7 +845,7 @@
} }
.api-modal-tabs { .api-modal-tabs {
@apply flex h-full max-w-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full; @apply flex h-full flex-col overflow-hidden rounded-md border bg-muted text-center;
} }
.api-modal-tablist-div { .api-modal-tablist-div {
@apply flex items-center justify-between px-2 py-2; @apply flex items-center justify-between px-2 py-2;
@ -902,7 +902,7 @@
@apply flex-max-width px-2 py-6 pl-4 pr-9; @apply flex-max-width px-2 py-6 pl-4 pr-9;
} }
.form-modal-chatbot-icon { .form-modal-chatbot-icon {
@apply mb-3 ml-3 mr-6 mt-1; @apply flex flex-col mb-3 ml-3 mr-6 mt-1;
} }
.form-modal-chat-image { .form-modal-chat-image {
@apply flex flex-col items-center gap-1; @apply flex flex-col items-center gap-1;

View file

@ -134,14 +134,15 @@ export type Component = {
}; };
export type VerticesOrderTypeAPI = { export type VerticesOrderTypeAPI = {
ids: Array<Array<string>>; ids: Array<string>;
run_id: string; run_id: string;
}; };
export type VertexBuildTypeAPI = { export type VertexBuildTypeAPI = {
id: string; id: string;
inactivated_vertices: Array<string> | null; inactivated_vertices: Array<string> | null;
activated_layers: Array<Array<string>> | null; next_vertices_ids: Array<string>;
run_id: string;
valid: boolean; valid: boolean;
params: string; params: string;
data: VertexDataTypeAPI; data: VertexDataTypeAPI;

View file

@ -113,7 +113,8 @@ export type CodeAreaComponentType = {
dynamic?: boolean; dynamic?: boolean;
id?: string; id?: string;
readonly?: boolean; readonly?: boolean;
openModal?: boolean; open?: boolean;
setOpen?: (open: boolean) => void;
}; };
export type FileComponentType = { export type FileComponentType = {
@ -519,7 +520,8 @@ export type codeAreaModalPropsType = {
children: ReactNode; children: ReactNode;
dynamic?: boolean; dynamic?: boolean;
readonly?: boolean; readonly?: boolean;
openModal?: boolean; open?: boolean;
setOpen?: (open: boolean) => void;
}; };
export type chatMessagePropsType = { export type chatMessagePropsType = {

View file

@ -90,10 +90,13 @@ export type FlowStoreType = {
onConnect: (connection: Connection) => void; onConnect: (connection: Connection) => void;
unselectAll: () => void; unselectAll: () => void;
buildFlow: ({ buildFlow: ({
nodeId, startNodeId,
stopNodeId,
input_value, input_value,
}: { }: {
nodeId?: string; nodeId?: string;
startNodeId?: string;
stopNodeId?: string;
input_value?: string; input_value?: string;
}) => Promise<void>; }) => Promise<void>;
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
@ -113,7 +116,9 @@ export type FlowStoreType = {
} | null; } | null;
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void; updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
revertBuiltStatusFromBuilding: () => void; revertBuiltStatusFromBuilding: () => void;
flowBuildStatus: { [key: string]: BuildStatus }; flowBuildStatus: {
[key: string]: { status: BuildStatus; timestamp?: string };
};
updateFlowPool: ( updateFlowPool: (
nodeId: string, nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType, data: FlowPoolObjectType | ChatOutputType | chatInputType,

View file

@ -8,7 +8,8 @@ import { VertexBuildTypeAPI } from "../types/api";
type BuildVerticesParams = { type BuildVerticesParams = {
flowId: string; // Assuming FlowType is the type for your flow flowId: string; // Assuming FlowType is the type for your flow
input_value?: any; // Replace any with the actual type if it's not any input_value?: any; // Replace any with the actual type if it's not any
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional startNodeId?: string | null; // Assuming nodeId is of type string, and it's optional
stopNodeId?: string | null; // Assuming nodeId is of type string, and it's optional
onGetOrderSuccess?: () => void; onGetOrderSuccess?: () => void;
onBuildUpdate?: ( onBuildUpdate?: (
data: VertexBuildTypeAPI, data: VertexBuildTypeAPI,
@ -32,7 +33,9 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
data: inactiveData, data: inactiveData,
params: "Inactive", params: "Inactive",
inactivated_vertices: null, inactivated_vertices: null,
activated_layers: null, run_id: "",
next_vertices_ids: [],
inactive_vertices: null,
valid: false, valid: false,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
@ -42,7 +45,8 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
export async function updateVerticesOrder( export async function updateVerticesOrder(
flowId: string, flowId: string,
nodeId: string | null startNodeId?: string | null,
stopNodeId?: string | null
): Promise<{ ): Promise<{
verticesLayers: string[][]; verticesLayers: string[][];
verticesIds: string[]; verticesIds: string[];
@ -52,7 +56,7 @@ export async function updateVerticesOrder(
const setErrorData = useAlertStore.getState().setErrorData; const setErrorData = useAlertStore.getState().setErrorData;
let orderResponse; let orderResponse;
try { try {
orderResponse = await getVerticesOrder(flowId, nodeId); orderResponse = await getVerticesOrder(flowId, startNodeId, stopNodeId);
} catch (error: any) { } catch (error: any) {
console.log(error); console.log(error);
setErrorData({ setErrorData({
@ -62,27 +66,26 @@ export async function updateVerticesOrder(
useFlowStore.getState().setIsBuilding(false); useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes"); throw new Error("Invalid nodes");
} }
let verticesOrder: Array<Array<string>> = orderResponse.data.ids; let verticesLayers: Array<Array<string>> = [orderResponse.data.ids];
let verticesLayers: Array<Array<string>> = [];
const runId = orderResponse.data.run_id; const runId = orderResponse.data.run_id;
if (nodeId) { // if (nodeId) {
for (let i = 0; i < verticesOrder.length; i += 1) { // for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i]; // const innerArray = verticesOrder[i];
const idIndex = innerArray.indexOf(nodeId); // const idIndex = innerArray.indexOf(nodeId);
if (idIndex !== -1) { // if (idIndex !== -1) {
// If there's a nodeId, we want to run just that component and not the entire layer // // If there's a nodeId, we want to run just that component and not the entire layer
// because a layer contains dependencies for the next layer // // because a layer contains dependencies for the next layer
// and we are stopping at the layer that contains the nodeId // // and we are stopping at the layer that contains the nodeId
verticesLayers.push([innerArray[idIndex]]); // verticesLayers.push([innerArray[idIndex]]);
break; // Stop searching after finding the first occurrence // break; // Stop searching after finding the first occurrence
} // }
// If the targetId is not found, include the entire inner array // // If the targetId is not found, include the entire inner array
verticesLayers.push(innerArray); // verticesLayers.push(innerArray);
} // }
} else { // } else {
verticesLayers = verticesOrder; // verticesLayers = verticesOrder;
} // }
const verticesIds = verticesOrder.flat(); const verticesIds = orderResponse.data.ids;
useFlowStore.getState().updateVerticesBuild({ useFlowStore.getState().updateVerticesBuild({
verticesLayers, verticesLayers,
verticesIds, verticesIds,
@ -95,7 +98,8 @@ export async function updateVerticesOrder(
export async function buildVertices({ export async function buildVertices({
flowId, flowId,
input_value, input_value,
nodeId = null, startNodeId,
stopNodeId,
onGetOrderSuccess, onGetOrderSuccess,
onBuildUpdate, onBuildUpdate,
onBuildComplete, onBuildComplete,
@ -104,9 +108,13 @@ export async function buildVertices({
validateNodes, validateNodes,
}: BuildVerticesParams) { }: BuildVerticesParams) {
let verticesBuild = useFlowStore.getState().verticesBuild; let verticesBuild = useFlowStore.getState().verticesBuild;
// if startNodeId and stopNodeId are provided
if (!verticesBuild || nodeId) { // something is wrong
verticesBuild = await updateVerticesOrder(flowId, nodeId); if (startNodeId && stopNodeId) {
return;
}
if (!verticesBuild || startNodeId || stopNodeId) {
verticesBuild = await updateVerticesOrder(flowId, startNodeId, stopNodeId);
} }
const verticesIds = verticesBuild?.verticesIds!; const verticesIds = verticesBuild?.verticesIds!;
@ -126,75 +134,57 @@ export async function buildVertices({
useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD); useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD);
useFlowStore.getState().setIsBuilding(true); useFlowStore.getState().setIsBuilding(true);
let dynamicVerticesLayers: Array<Array<string>> = [...verticesLayers]; let currentLayerIndex = 0; // Start with the first layer
const handleBuildUpdate = (data: VertexBuildTypeAPI, status: BuildStatus) => {
// Handle activated vertices
console.log("handleBuildUpdate", data, status);
if (data.activated_layers && data.activated_layers.length > 0) {
const thisVertexLayer = dynamicVerticesLayers.findIndex((layer) =>
layer.includes(data.id)
);
let nextLayerIndex = thisVertexLayer + 1;
console.log("nextLayerIndex", nextLayerIndex);
console.log("dynamicVerticesLayers", dynamicVerticesLayers);
// This adds layers to the dynamicVerticesLayers array
// starting from the index of the current layer + 1
data.activated_layers.forEach((newLayer) => {
if (!dynamicVerticesLayers[nextLayerIndex]) {
dynamicVerticesLayers[nextLayerIndex] = [];
}
dynamicVerticesLayers[nextLayerIndex] = [
...dynamicVerticesLayers[nextLayerIndex],
...newLayer,
];
nextLayerIndex += 1;
});
// Let's implement one that just adds all layers to the end of the array
// data.activated_layers.forEach((newLayer) => {
// // filter the newLayer to remove any vertices that are already in the dynamicVerticesLayers
// // after thisVertexLayer
// newLayer = newLayer.filter((vertex) => {
// return !dynamicVerticesLayers
// .slice(thisVertexLayer)
// .flat()
// .includes(vertex);
// });
// if (newLayer.length > 0) {
// console.log("newLayer after filter", newLayer);
// dynamicVerticesLayers.push(newLayer);
// }
// });
}
if (onBuildUpdate) onBuildUpdate(data, status, runId);
};
// Set each vertex state to building // Set each vertex state to building
const buildResults: Array<boolean> = []; const buildResults: Array<boolean> = [];
for (let i = 0; i < dynamicVerticesLayers.length; i++) {
const layer = dynamicVerticesLayers[i];
if (onBuildStart) onBuildStart(layer);
for (const id of layer) { // Build each layer
while (
currentLayerIndex <
(useFlowStore.getState().verticesBuild?.verticesLayers! || []).length
) {
// Get the current layer
const currentLayer =
useFlowStore.getState().verticesBuild?.verticesLayers![currentLayerIndex];
// If there are no more layers, we are done
if (!currentLayer) {
if (onBuildComplete) {
const allNodesValid = buildResults.every((result) => result);
onBuildComplete(allNodesValid);
useFlowStore.getState().setIsBuilding(false);
}
return;
}
// If there is a callback for the start of the build, call it
if (onBuildStart) onBuildStart(currentLayer);
// Build each vertex in the current layer
await Promise.all(
currentLayer.map(async (vertexId) => {
// Check if id is in the list of inactive nodes // Check if id is in the list of inactive nodes
// useFlowStore because it gets updated constantly
if ( if (
!useFlowStore.getState().verticesBuild?.verticesIds.includes(id) && !useFlowStore
.getState()
.verticesBuild?.verticesIds.includes(vertexId) &&
onBuildUpdate onBuildUpdate
) { ) {
// If it is, skip building and set the state to inactive // If it is, skip building and set the state to inactive
console.log("inactive", id); onBuildUpdate(
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE, runId); getInactiveVertexData(vertexId),
BuildStatus.INACTIVE,
runId
);
buildResults.push(false); buildResults.push(false);
continue; return;
} }
// Build the vertex
await buildVertex({ await buildVertex({
flowId, flowId,
id, id: vertexId,
input_value, input_value,
onBuildUpdate: handleBuildUpdate, onBuildUpdate: (data: VertexBuildTypeAPI, status: BuildStatus) => {
if (onBuildUpdate) onBuildUpdate(data, status, runId);
},
onBuildError, onBuildError,
verticesIds, verticesIds,
buildResults, buildResults,
@ -203,13 +193,16 @@ export async function buildVertices({
}, },
}); });
if (stop) { if (stop) {
break; return;
}
} }
})
);
// Once the current layer is built, move to the next layer
currentLayerIndex += 1;
if (stop) { if (stop) {
break; break;
} }
}
if (onBuildComplete) { if (onBuildComplete) {
const allNodesValid = buildResults.every((result) => result); const allNodesValid = buildResults.every((result) => result);
@ -217,7 +210,7 @@ export async function buildVertices({
useFlowStore.getState().setIsBuilding(false); useFlowStore.getState().setIsBuilding(false);
} }
} }
}
async function buildVertex({ async function buildVertex({
flowId, flowId,
id, id,

View file

@ -12,8 +12,8 @@ import {
INPUT_TYPES, INPUT_TYPES,
LANGFLOW_SUPPORTED_TYPES, LANGFLOW_SUPPORTED_TYPES,
OUTPUT_TYPES, OUTPUT_TYPES,
SUCCESS_BUILD,
specialCharsRegex, specialCharsRegex,
successBuild,
} from "../constants/constants"; } from "../constants/constants";
import { downloadFlowsFromDatabase } from "../controllers/API"; import { downloadFlowsFromDatabase } from "../controllers/API";
import { import {
@ -1092,7 +1092,7 @@ export function getGroupStatus(
flow: FlowType, flow: FlowType,
ssData: { [key: string]: { valid: boolean; params: string } } ssData: { [key: string]: { valid: boolean; params: string } }
) { ) {
let status = { valid: true, params: successBuild }; let status = { valid: true, params: SUCCESS_BUILD };
const { nodes } = flow.data!; const { nodes } = flow.data!;
const ids = nodes.map((n: NodeType) => n.data.id); const ids = nodes.map((n: NodeType) => n.data.id);
ids.forEach((id) => { ids.forEach((id) => {

View file

@ -81,6 +81,7 @@ import {
Network, Network,
Paperclip, Paperclip,
Pencil, Pencil,
PencilLine,
Pin, Pin,
Play, Play,
Plus, Plus,
@ -418,6 +419,7 @@ export const nodeIconsLucide: iconsType = {
Group, Group,
LogIn, LogIn,
ChevronUp, ChevronUp,
PencilLine,
Ungroup, Ungroup,
BookMarked, BookMarked,
Minus, Minus,

View file

@ -640,7 +640,5 @@ export function getFieldTitle(
): string { ): string {
return template[templateField].display_name return template[templateField].display_name
? template[templateField].display_name! ? template[templateField].display_name!
: template[templateField].name : template[templateField].name ?? templateField;
? toTitleCase(template[templateField].name!, true)
: toTitleCase(templateField, true);
} }