Feat: Implement Multiple Shortcuts for Enhanced User Experience (#1489)
This pull request introduces a set of multiple keyboard shortcuts to enhance the usability and efficiency of the application. These are some of the shortcuts that have been implemented: Ctrl + E: Open edit node modal Ctrl + Q: Minimize/expand node Ctrl + U: Ungroup Ctrl + Shift + S: Share component Ctrl + Shift + D: Open component docs Ctrl + S: Save component These shortcuts provide users with quick access to commonly used features, enabling smoother navigation and faster interaction within the application.
This commit is contained in:
commit
eb32a9f5d6
18 changed files with 306 additions and 175 deletions
|
|
@ -101,12 +101,8 @@ async def build_vertex(
|
||||||
cache = chat_service.get_cache(flow_id)
|
cache = chat_service.get_cache(flow_id)
|
||||||
if not cache:
|
if not cache:
|
||||||
# If there's no cache
|
# If there's no cache
|
||||||
logger.warning(
|
logger.warning(f"No cache found for {flow_id}. Building graph starting at {vertex_id}")
|
||||||
f"No cache found for {flow_id}. Building graph starting at {vertex_id}"
|
graph = build_and_cache_graph(flow_id=flow_id, session=next(get_session()), chat_service=chat_service)
|
||||||
)
|
|
||||||
graph = build_and_cache_graph(
|
|
||||||
flow_id=flow_id, session=next(get_session()), chat_service=chat_service
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
result_data_response = ResultDataResponse(results={})
|
result_data_response = ResultDataResponse(results={})
|
||||||
|
|
@ -211,9 +207,7 @@ async def build_vertex_stream(
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
else:
|
else:
|
||||||
session_data = await session_service.load_session(
|
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||||
session_id, flow_id=flow_id
|
|
||||||
)
|
|
||||||
graph, artifacts = session_data if session_data else (None, None)
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
if not graph:
|
if not graph:
|
||||||
raise ValueError(f"No graph found for {flow_id}.")
|
raise ValueError(f"No graph found for {flow_id}.")
|
||||||
|
|
|
||||||
|
|
@ -264,9 +264,7 @@ class Graph:
|
||||||
def build_parent_child_map(self):
|
def build_parent_child_map(self):
|
||||||
parent_child_map = defaultdict(list)
|
parent_child_map = defaultdict(list)
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
parent_child_map[vertex.id] = [
|
parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
|
||||||
child.id for child in self.get_successors(vertex)
|
|
||||||
]
|
|
||||||
return parent_child_map
|
return parent_child_map
|
||||||
|
|
||||||
def increment_run_count(self):
|
def increment_run_count(self):
|
||||||
|
|
@ -480,11 +478,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
self.vertices.remove(vertex)
|
self.vertices.remove(vertex)
|
||||||
self.vertex_map.pop(vertex_id)
|
self.vertex_map.pop(vertex_id)
|
||||||
self.edges = [
|
self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id]
|
||||||
edge
|
|
||||||
for edge in self.edges
|
|
||||||
if edge.source_id != vertex_id and edge.target_id != vertex_id
|
|
||||||
]
|
|
||||||
|
|
||||||
def _build_vertex_params(self) -> None:
|
def _build_vertex_params(self) -> None:
|
||||||
"""Identifies and handles the LLM vertex within the graph."""
|
"""Identifies and handles the LLM vertex within the graph."""
|
||||||
|
|
@ -505,9 +499,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
if not self._validate_vertex(vertex):
|
if not self._validate_vertex(vertex):
|
||||||
raise ValueError(
|
raise ValueError(f"{vertex.display_name} is not connected to any other components")
|
||||||
f"{vertex.display_name} is not connected to any other components"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_vertex(self, vertex: Vertex) -> bool:
|
def _validate_vertex(self, vertex: Vertex) -> bool:
|
||||||
"""Validates a vertex."""
|
"""Validates a vertex."""
|
||||||
|
|
@ -613,9 +605,7 @@ class Graph:
|
||||||
def dfs(vertex):
|
def dfs(vertex):
|
||||||
if state[vertex] == 1:
|
if state[vertex] == 1:
|
||||||
# We have a cycle
|
# We have a cycle
|
||||||
raise ValueError(
|
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||||
"Graph contains a cycle, cannot perform topological sort"
|
|
||||||
)
|
|
||||||
if state[vertex] == 0:
|
if state[vertex] == 0:
|
||||||
state[vertex] = 1
|
state[vertex] = 1
|
||||||
for edge in vertex.edges:
|
for edge in vertex.edges:
|
||||||
|
|
@ -639,10 +629,7 @@ class Graph:
|
||||||
|
|
||||||
def get_predecessors(self, vertex):
|
def get_predecessors(self, vertex):
|
||||||
"""Returns the predecessors of a vertex."""
|
"""Returns the predecessors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(source_id)
|
|
||||||
for source_id in self.predecessor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_all_successors(self, vertex, recursive=True, flat=True):
|
def get_all_successors(self, vertex, recursive=True, flat=True):
|
||||||
# Recursively get the successors of the current vertex
|
# Recursively get the successors of the current vertex
|
||||||
|
|
@ -683,10 +670,7 @@ class Graph:
|
||||||
|
|
||||||
def get_successors(self, vertex):
|
def get_successors(self, vertex):
|
||||||
"""Returns the successors of a vertex."""
|
"""Returns the successors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(target_id)
|
|
||||||
for target_id in self.successor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
||||||
"""Returns the neighbors of a vertex."""
|
"""Returns the neighbors of a vertex."""
|
||||||
|
|
@ -732,9 +716,7 @@ class Graph:
|
||||||
edges_added.add((source.id, target.id))
|
edges_added.add((source.id, target.id))
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_vertex_class(
|
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
|
||||||
self, node_type: str, node_base_type: str, node_id: str
|
|
||||||
) -> Type[Vertex]:
|
|
||||||
"""Returns the node class based on the node type."""
|
"""Returns the node class based on the node type."""
|
||||||
# First we check for the node_base_type
|
# First we check for the node_base_type
|
||||||
node_name = node_id.split("-")[0]
|
node_name = node_id.split("-")[0]
|
||||||
|
|
@ -767,18 +749,14 @@ class Graph:
|
||||||
vertex_type: str = vertex_data["type"] # type: ignore
|
vertex_type: str = vertex_data["type"] # type: ignore
|
||||||
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
||||||
|
|
||||||
VertexClass = self._get_vertex_class(
|
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
|
||||||
vertex_type, vertex_base_type, vertex_data["id"]
|
|
||||||
)
|
|
||||||
vertex_instance = VertexClass(vertex, graph=self)
|
vertex_instance = VertexClass(vertex, graph=self)
|
||||||
vertex_instance.set_top_level(self.top_level_vertices)
|
vertex_instance.set_top_level(self.top_level_vertices)
|
||||||
vertices.append(vertex_instance)
|
vertices.append(vertex_instance)
|
||||||
|
|
||||||
return vertices
|
return vertices
|
||||||
|
|
||||||
def get_children_by_vertex_type(
|
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
||||||
self, vertex: Vertex, vertex_type: str
|
|
||||||
) -> List[Vertex]:
|
|
||||||
"""Returns the children of a vertex based on the vertex type."""
|
"""Returns the children of a vertex based on the vertex type."""
|
||||||
children = []
|
children = []
|
||||||
vertex_types = [vertex.data["type"]]
|
vertex_types = [vertex.data["type"]]
|
||||||
|
|
@ -790,9 +768,7 @@ class Graph:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
vertex_ids = [vertex.id for vertex in self.vertices]
|
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||||
edges_repr = "\n".join(
|
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||||
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
|
|
||||||
)
|
|
||||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
||||||
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
||||||
|
|
@ -935,9 +911,7 @@ class Graph:
|
||||||
|
|
||||||
return refined_layers
|
return refined_layers
|
||||||
|
|
||||||
def sort_chat_inputs_first(
|
def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
chat_inputs_first = []
|
chat_inputs_first = []
|
||||||
for layer in vertices_layers:
|
for layer in vertices_layers:
|
||||||
for vertex_id in layer:
|
for vertex_id in layer:
|
||||||
|
|
@ -991,15 +965,11 @@ class Graph:
|
||||||
self.vertices_to_run.remove(vertex_id)
|
self.vertices_to_run.remove(vertex_id)
|
||||||
return should_run
|
return should_run
|
||||||
|
|
||||||
def sort_interface_components_first(
|
def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
||||||
|
|
||||||
def contains_interface_component(vertex):
|
def contains_interface_component(vertex):
|
||||||
return any(
|
return any(component.value in vertex for component in InterfaceComponentTypes)
|
||||||
component.value in vertex for component in InterfaceComponentTypes
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
||||||
sorted_vertices = [
|
sorted_vertices = [
|
||||||
|
|
@ -1011,22 +981,16 @@ class Graph:
|
||||||
]
|
]
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
||||||
def sort_by_avg_build_time(
|
def sort_by_avg_build_time(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
|
|
||||||
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
if len(vertices_ids) == 1:
|
if len(vertices_ids) == 1:
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
vertices_ids.sort(
|
vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
|
||||||
key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time
|
|
||||||
)
|
|
||||||
|
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
|
|
||||||
sorted_vertices = [
|
sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
|
||||||
sort_layer_by_avg_build_time(layer) for layer in vertices_layers
|
|
||||||
]
|
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,7 @@ class Vertex:
|
||||||
|
|
||||||
def set_state(self, state: str):
|
def set_state(self, state: str):
|
||||||
self.state = VertexStates[state]
|
self.state = VertexStates[state]
|
||||||
if (
|
if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
|
||||||
self.state == VertexStates.INACTIVE
|
|
||||||
and self.graph.in_degree_map[self.id] < 2
|
|
||||||
):
|
|
||||||
# If the vertex is inactive and has only one in degree
|
# If the vertex is inactive and has only one in degree
|
||||||
# it means that it is not a merge point in the graph
|
# it means that it is not a merge point in the graph
|
||||||
self.graph.inactivated_vertices.add(self.id)
|
self.graph.inactivated_vertices.add(self.id)
|
||||||
|
|
@ -133,9 +130,7 @@ class Vertex:
|
||||||
# If the Vertex.type is a power component
|
# If the Vertex.type is a power component
|
||||||
# then we need to return the built object
|
# then we need to return the built object
|
||||||
# instead of the result dict
|
# instead of the result dict
|
||||||
if self.is_interface_component and not isinstance(
|
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
|
||||||
self._built_object, UnbuiltObject
|
|
||||||
):
|
|
||||||
result = self._built_object
|
result = self._built_object
|
||||||
# if it is not a dict or a string and hasattr model_dump then
|
# if it is not a dict or a string and hasattr model_dump then
|
||||||
# return the model_dump
|
# return the model_dump
|
||||||
|
|
@ -147,11 +142,7 @@ class Vertex:
|
||||||
|
|
||||||
if isinstance(self._built_result, UnbuiltResult):
|
if isinstance(self._built_result, UnbuiltResult):
|
||||||
return {}
|
return {}
|
||||||
return (
|
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
|
||||||
self._built_result
|
|
||||||
if isinstance(self._built_result, dict)
|
|
||||||
else {"result": self._built_result}
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_artifacts(self) -> None:
|
def set_artifacts(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -221,31 +212,19 @@ class Vertex:
|
||||||
self.selected_output_type = self.data["node"].get("selected_output_type")
|
self.selected_output_type = self.data["node"].get("selected_output_type")
|
||||||
self.is_input = self.data["node"].get("is_input") or self.is_input
|
self.is_input = self.data["node"].get("is_input") or self.is_input
|
||||||
self.is_output = self.data["node"].get("is_output") or self.is_output
|
self.is_output = self.data["node"].get("is_output") or self.is_output
|
||||||
template_dicts = {
|
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_session_id = "session_id" in template_dicts
|
self.has_session_id = "session_id" in template_dicts
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if value["required"]
|
|
||||||
]
|
]
|
||||||
self.optional_inputs = [
|
self.optional_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if not value["required"]
|
|
||||||
]
|
]
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
self.optional_inputs.extend(
|
self.optional_inputs.extend(
|
||||||
[
|
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
||||||
input_type
|
|
||||||
for value in template_dicts.values()
|
|
||||||
for input_type in value.get("input_types", [])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
|
|
@ -292,11 +271,7 @@ class Vertex:
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
return
|
return
|
||||||
|
|
||||||
template_dict = {
|
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -356,11 +331,7 @@ class Vertex:
|
||||||
# list of dicts, so we need to convert it to a dict
|
# list of dicts, so we need to convert it to a dict
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
params[key] = {
|
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||||
k: v
|
|
||||||
for item in value.get("value", [])
|
|
||||||
for k, v in item.items()
|
|
||||||
}
|
|
||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
params[key] = val
|
params[key] = val
|
||||||
elif value.get("type") == "int" and val is not None:
|
elif value.get("type") == "int" and val is not None:
|
||||||
|
|
@ -485,9 +456,7 @@ class Vertex:
|
||||||
if isinstance(self._built_object, str):
|
if isinstance(self._built_object, str):
|
||||||
self._built_result = self._built_object
|
self._built_result = self._built_object
|
||||||
|
|
||||||
result = await generate_result(
|
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
|
||||||
self._built_object, inputs, self.has_external_output, session_id
|
|
||||||
)
|
|
||||||
self._built_result = result
|
self._built_result = result
|
||||||
|
|
||||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||||
|
|
@ -532,9 +501,7 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
return all(self._is_node(node) for node in value)
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
async def get_result(
|
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
|
||||||
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
|
|
||||||
) -> Any:
|
|
||||||
# PLEASE REVIEW THIS IF STATEMENT
|
# PLEASE REVIEW THIS IF STATEMENT
|
||||||
# Check if the Vertex was built already
|
# Check if the Vertex was built already
|
||||||
if self._built:
|
if self._built:
|
||||||
|
|
@ -568,9 +535,7 @@ class Vertex:
|
||||||
self._extend_params_list_with_result(key, result)
|
self._extend_params_list_with_result(key, result)
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
|
|
||||||
async def _build_list_of_nodes_and_update_params(
|
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||||
self, key, nodes: List["Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -637,9 +602,7 @@ class Vertex:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
|
||||||
f"Error building node {self.display_name}: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -732,24 +695,16 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
# Get the requester edge
|
# Get the requester edge
|
||||||
requester_edge = next(
|
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
|
||||||
(edge for edge in self.edges if edge.target_id == requester.id), None
|
|
||||||
)
|
|
||||||
# Return the result of the requester edge
|
# Return the result of the requester edge
|
||||||
return (
|
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
|
||||||
None
|
|
||||||
if requester_edge is None
|
|
||||||
else await requester_edge.get_result(source=self, target=requester)
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_edge(self, edge: "ContractEdge") -> None:
|
def add_edge(self, edge: "ContractEdge") -> None:
|
||||||
if edge not in self.edges:
|
if edge not in self.edges:
|
||||||
self.edges.append(edge)
|
self.edges.append(edge)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
||||||
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ def add_output_types(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -77,18 +75,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
|
||||||
frontend_node.field_order = field_order
|
frontend_node.field_order = field_order
|
||||||
|
|
||||||
|
|
||||||
def add_base_classes(
|
def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
|
||||||
):
|
|
||||||
"""Add base classes to the frontend node"""
|
"""Add base classes to the frontend node"""
|
||||||
for return_type_instance in return_types:
|
for return_type_instance in return_types:
|
||||||
if return_type_instance is None:
|
if return_type_instance is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -176,9 +170,7 @@ def add_new_custom_field(
|
||||||
)
|
)
|
||||||
|
|
||||||
if "name" in field_config:
|
if "name" in field_config:
|
||||||
warnings.warn(
|
warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
|
||||||
"The 'name' key in field_config is used to build the object and can't be changed."
|
|
||||||
)
|
|
||||||
required = field_config.pop("required", field_required)
|
required = field_config.pop("required", field_required)
|
||||||
placeholder = field_config.pop("placeholder", "")
|
placeholder = field_config.pop("placeholder", "")
|
||||||
|
|
||||||
|
|
@ -277,9 +269,7 @@ def run_build_config(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||||
"Invalid type convertion. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -397,16 +387,10 @@ def build_custom_component_template(
|
||||||
|
|
||||||
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
||||||
|
|
||||||
frontend_node = add_code_field(
|
frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
|
||||||
frontend_node, custom_component.code, field_config.get("code", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
add_base_classes(
|
add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
)
|
|
||||||
add_output_types(
|
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
|
||||||
)
|
|
||||||
|
|
||||||
reorder_fields(frontend_node, custom_instance._get_field_order())
|
reorder_fields(frontend_node, custom_instance._get_field_order())
|
||||||
|
|
||||||
|
|
@ -455,9 +439,7 @@ def build_custom_components(components_paths: List[str]):
|
||||||
custom_component_dict = build_custom_component_list_from_path(path_str)
|
custom_component_dict = build_custom_component_list_from_path(path_str)
|
||||||
if custom_component_dict:
|
if custom_component_dict:
|
||||||
category = next(iter(custom_component_dict))
|
category = next(iter(custom_component_dict))
|
||||||
logger.info(
|
logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
|
||||||
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
|
||||||
)
|
|
||||||
custom_components_from_file = merge_nested_dicts_with_renaming(
|
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||||
custom_components_from_file, custom_component_dict
|
custom_components_from_file, custom_component_dict
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,9 @@ export default function GenericNode({
|
||||||
}
|
}
|
||||||
}, [validationStatus, validationStatus?.params]);
|
}, [validationStatus, validationStatus?.params]);
|
||||||
|
|
||||||
const showNode = data.showNode ?? true;
|
// const showNode = data.showNode ?? true;
|
||||||
|
|
||||||
|
const [showNode, setShowNode] = useState(data.showNode ?? true);
|
||||||
|
|
||||||
const nameEditable = true;
|
const nameEditable = true;
|
||||||
|
|
||||||
|
|
@ -298,6 +300,7 @@ export default function GenericNode({
|
||||||
data: { ...old.data, showNode: show },
|
data: { ...old.data, showNode: show },
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
setShowState={setShowNode}
|
||||||
numberOfHandles={handles}
|
numberOfHandles={handles}
|
||||||
showNode={showNode}
|
showNode={showNode}
|
||||||
openAdvancedModal={false}
|
openAdvancedModal={false}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 39 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 53 KiB |
|
|
@ -135,6 +135,15 @@ export const MenuBar = ({
|
||||||
>
|
>
|
||||||
<IconComponent name="Undo" className="header-menu-options " />
|
<IconComponent name="Undo" className="header-menu-options " />
|
||||||
Undo
|
Undo
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.4em]">Z</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -144,6 +153,15 @@ export const MenuBar = ({
|
||||||
>
|
>
|
||||||
<IconComponent name="Redo" className="header-menu-options " />
|
<IconComponent name="Redo" className="header-menu-options " />
|
||||||
Redo
|
Redo
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.4em]">Y</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const SelectContent = React.forwardRef<
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"relative z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg"
|
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg"
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg"
|
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg"
|
||||||
|
//@ts-ignore
|
||||||
|
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg"
|
||||||
|
//@ts-ignore
|
||||||
|
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg"
|
||||||
|
//@ts-ignore
|
||||||
|
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg"
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { FlowType } from "../../types/flow"
|
import { FlowType } from "../../types/flow"
|
||||||
import { updateIds } from "../../utils/reactflowUtils";
|
import { updateIds } from "../../utils/reactflowUtils";
|
||||||
|
|
@ -24,7 +29,13 @@ export default function UndrawCardComponent({
|
||||||
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
case "Basic Prompting":
|
case "Basic Prompting":
|
||||||
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
default:
|
case "Chat with memory":
|
||||||
|
return <ChatWithHistory style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
|
case "API requests":
|
||||||
|
return <APIRequest style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
|
case "Assistant":
|
||||||
|
return <Assistant style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
|
default:
|
||||||
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ function ConfirmationModal({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
|
<BaseModal size={size} open={open} setOpen={setModalOpen}>
|
||||||
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
|
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={titleHeader ?? null}>
|
<BaseModal.Header description={titleHeader ?? null}>
|
||||||
<span className="pr-2">{title}</span>
|
<span className="pr-2">{title}</span>
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,13 @@ export default function CodeAreaModal({
|
||||||
: useState(false);
|
: 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);
|
||||||
|
|
||||||
const [height, setHeight] = useState<string | null>(null);
|
const [height, setHeight] = useState<string | null>(null);
|
||||||
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 [error, setError] = useState<{
|
const [error, setError] = useState<{
|
||||||
detail: CodeErrorDataTypeAPI;
|
detail: CodeErrorDataTypeAPI;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if nodeClass.template has more fields other than code and dynamic is true
|
// if nodeClass.template has more fields other than code and dynamic is true
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
updateFlowPosition,
|
updateFlowPosition,
|
||||||
} from "../../../../utils/reactflowUtils";
|
} from "../../../../utils/reactflowUtils";
|
||||||
import { classNames, cn } from "../../../../utils/utils";
|
import { classNames, cn } from "../../../../utils/utils";
|
||||||
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
|
|
||||||
export default function NodeToolbarComponent({
|
export default function NodeToolbarComponent({
|
||||||
data,
|
data,
|
||||||
|
|
@ -37,6 +38,7 @@ export default function NodeToolbarComponent({
|
||||||
showNode,
|
showNode,
|
||||||
name = "code",
|
name = "code",
|
||||||
selected,
|
selected,
|
||||||
|
setShowState,
|
||||||
onCloseAdvancedModal,
|
onCloseAdvancedModal,
|
||||||
}: nodeToolbarPropsType): JSX.Element {
|
}: nodeToolbarPropsType): JSX.Element {
|
||||||
const nodeLength = Object.keys(data.node!.template).filter(
|
const nodeLength = Object.keys(data.node!.template).filter(
|
||||||
|
|
@ -58,6 +60,7 @@ export default function NodeToolbarComponent({
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
|
const [updateMinimize, setUpdateMinimize] = useState(showNode);
|
||||||
|
|
||||||
const isMinimal = numberOfHandles <= 1;
|
const isMinimal = numberOfHandles <= 1;
|
||||||
const isGroup = data.node?.flow ? true : false;
|
const isGroup = data.node?.flow ? true : false;
|
||||||
|
|
@ -68,7 +71,7 @@ export default function NodeToolbarComponent({
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const setEdges = useFlowStore((state) => state.setEdges);
|
||||||
|
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
const version = useDarkStore((state) => state.version);
|
const version = useDarkStore((state) => state.version);
|
||||||
|
|
@ -93,6 +96,10 @@ export default function NodeToolbarComponent({
|
||||||
const setLastCopiedSelection = useFlowStore(
|
const setLastCopiedSelection = useFlowStore(
|
||||||
(state) => state.setLastCopiedSelection
|
(state) => state.setLastCopiedSelection
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setSuccessData = useAlertStore(state => state.setSuccessData);
|
||||||
|
const setNoticeData = useAlertStore(state => state.setNoticeData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -222,7 +229,105 @@ export default function NodeToolbarComponent({
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const hasCode = Object.keys(data.node!.template).includes("code");
|
const hasCode = Object.keys(data.node!.template).includes("code");
|
||||||
console.log(hasCode)
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
isGroup &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "u"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
takeSnapshot();
|
||||||
|
expandGroupNode(
|
||||||
|
data.id,
|
||||||
|
updateFlowPosition(position, data.node?.flow!),
|
||||||
|
data.node!.template,
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
setNodes,
|
||||||
|
setEdges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
(hasApiKey || hasStore) &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.shiftKey &&
|
||||||
|
event.key === "S"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
setShowconfirmShare((state) => !state);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "q"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isMinimal) {
|
||||||
|
setShowState(show => !show)
|
||||||
|
setShowNode(data.showNode ?? true ? false : true);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setNoticeData({title: "Minimization are only available for nodes with one handle or fewer."});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.shiftKey &&
|
||||||
|
event.key === "C"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (hasCode) return setOpenModal((state) => !state);
|
||||||
|
setNoticeData({title: `You can not access ${data.id} code`})
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
!isGroup &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "e"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
setShowModalAdvanced((state) => !state);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "s"
|
||||||
|
) {
|
||||||
|
if (isSaved) {
|
||||||
|
event.preventDefault();
|
||||||
|
return setShowOverrideModal((state) => !state);
|
||||||
|
}
|
||||||
|
if (hasCode) {
|
||||||
|
event.preventDefault();
|
||||||
|
saveComponent(cloneDeep(data), false);
|
||||||
|
setSuccessData({title: `${data.id} saved successfully`})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selected &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.shiftKey &&
|
||||||
|
event.key === "D"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (data.node?.documentation) {
|
||||||
|
return openInNewTab(data.node?.documentation);
|
||||||
|
}
|
||||||
|
setNoticeData({title: `${data.id} docs is not available at the moment.`})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}, [isSaved, showNode, data.showNode, isMinimal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-26 h-10">
|
<div className="w-26 h-10">
|
||||||
|
|
@ -244,7 +349,7 @@ export default function NodeToolbarComponent({
|
||||||
<ShadTooltip content={"Save"} side="top">
|
<ShadTooltip content={"Save"} side="top">
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
|
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", (hasCode ? " " : " rounded-l-md ")
|
||||||
)}
|
)}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -312,19 +417,37 @@ export default function NodeToolbarComponent({
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{nodeLength > 0 && (
|
{nodeLength > 0 && (
|
||||||
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
||||||
<div className="flex" data-testid="edit-button-modal">
|
<div className="flex">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Settings2"
|
name="Settings2"
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Edit{" "}
|
<span className="">Edit</span>{" "}
|
||||||
</div>{" "}
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.46em]">E</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
<SelectItem value={"duplicate"}>
|
<SelectItem value={"duplicate"}>
|
||||||
<div className="flex" data-testid="save-button-modal">
|
<div className="flex" data-testid="save-button-modal">
|
||||||
<IconComponent name="Copy" className="relative top-0.5 mr-2 h-4 w-4" />
|
<IconComponent name="Copy" className="relative top-0.5 mr-2 h-4 w-4" />
|
||||||
Duplicate
|
Duplicate
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.20rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.4em]">D</span>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value={"copy"}>
|
<SelectItem value={"copy"}>
|
||||||
|
|
@ -334,11 +457,15 @@ export default function NodeToolbarComponent({
|
||||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="">Copy</span>{" "}
|
<span className="">Copy</span>{" "}
|
||||||
<IconComponent
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
name="Command"
|
<IconComponent
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
name="Command"
|
||||||
></IconComponent>
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
<span className="absolute right-2 top-[0.5em]">C</span>
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.4em]">C</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{hasStore && (
|
{hasStore && (
|
||||||
|
|
@ -352,6 +479,20 @@ export default function NodeToolbarComponent({
|
||||||
className="relative top-0.5 -m-1 mr-1 h-6 w-6"
|
className="relative top-0.5 -m-1 mr-1 h-6 w-6"
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Share{" "}
|
Share{" "}
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<IconComponent
|
||||||
|
name="ArrowBigUp"
|
||||||
|
className="absolute right-[1.09rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
<span className="absolute right-2 top-[0.45em]">S</span>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|
@ -373,10 +514,23 @@ export default function NodeToolbarComponent({
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="FileText"
|
name="FileText"
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Docs
|
<span className="">Docs</span>{" "}
|
||||||
</div>{" "}
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
|
||||||
|
)}
|
||||||
|
<IconComponent
|
||||||
|
name="ArrowBigUp"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
<span className="absolute right-2 top-[0.43em]">D</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{isMinimal && (
|
{isMinimal && (
|
||||||
<SelectItem value={"show"}>
|
<SelectItem value={"show"}>
|
||||||
|
|
@ -386,6 +540,15 @@ export default function NodeToolbarComponent({
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
className="relative top-0.5 mr-2 h-4 w-4"
|
||||||
/>
|
/>
|
||||||
{showNode ? "Minimize" : "Expand"}
|
{showNode ? "Minimize" : "Expand"}
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.46em]">Q</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|
@ -393,10 +556,19 @@ export default function NodeToolbarComponent({
|
||||||
<SelectItem value="ungroup">
|
<SelectItem value="ungroup">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Combine"
|
name="Ungroup"
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Ungroup{" "}
|
<span className="">Ungroup</span>{" "}
|
||||||
|
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||||
|
<IconComponent
|
||||||
|
name="Command"
|
||||||
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
></IconComponent>
|
||||||
|
) : (
|
||||||
|
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||||
|
)}
|
||||||
|
<span className="absolute right-2 top-[0.43em]">U</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|
@ -429,9 +601,13 @@ export default function NodeToolbarComponent({
|
||||||
index={6}
|
index={6}
|
||||||
onConfirm={(index, user) => {
|
onConfirm={(index, user) => {
|
||||||
saveComponent(cloneDeep(data), true);
|
saveComponent(cloneDeep(data), true);
|
||||||
|
setSuccessData({title: `${data.id} successfully overridden!`})
|
||||||
}}
|
}}
|
||||||
onClose={setShowOverrideModal}
|
onClose={setShowOverrideModal}
|
||||||
onCancel={() => saveComponent(cloneDeep(data), false)}
|
onCancel={() => {
|
||||||
|
saveComponent(cloneDeep(data), false)
|
||||||
|
setSuccessData({title: "New node successfully saved!"})
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ConfirmationModal.Content>
|
<ConfirmationModal.Content>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
||||||
15
src/frontend/src/stores/shortcuts.ts
Normal file
15
src/frontend/src/stores/shortcuts.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { shortcutsStoreType } from "../types/store";
|
||||||
|
|
||||||
|
export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
|
||||||
|
openCodeModalWShortcut: false,
|
||||||
|
handleModalWShortcut: (modal) => {
|
||||||
|
switch (modal) {
|
||||||
|
case "code":
|
||||||
|
set({
|
||||||
|
openCodeModalWShortcut: !get().openCodeModalWShortcut,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReactElement, ReactNode } from "react";
|
import { ReactElement, ReactNode, SetStateAction } from "react";
|
||||||
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||||
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
|
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
|
||||||
import { ChatMessageType } from "../chat";
|
import { ChatMessageType } from "../chat";
|
||||||
|
|
@ -107,6 +107,7 @@ export type PromptAreaComponentType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CodeAreaComponentType = {
|
export type CodeAreaComponentType = {
|
||||||
|
setOpenModal?: (bool: boolean) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onChange: (value: string[] | string) => void;
|
onChange: (value: string[] | string) => void;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -490,6 +491,7 @@ export type nodeToolbarPropsType = {
|
||||||
openAdvancedModal?: boolean;
|
openAdvancedModal?: boolean;
|
||||||
onCloseAdvancedModal?: (close: boolean) => void;
|
onCloseAdvancedModal?: (close: boolean) => void;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
setShowState: (show: boolean | SetStateAction<boolean>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type parsedDataType = {
|
export type parsedDataType = {
|
||||||
|
|
@ -517,6 +519,7 @@ export type modalHeaderType = {
|
||||||
|
|
||||||
export type codeAreaModalPropsType = {
|
export type codeAreaModalPropsType = {
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
|
setOpenModal?: (bool: boolean) => void;
|
||||||
value: string;
|
value: string;
|
||||||
nodeClass: APIClassType | undefined;
|
nodeClass: APIClassType | undefined;
|
||||||
setNodeClass: (Class: APIClassType, code?: string) => void | undefined;
|
setNodeClass: (Class: APIClassType, code?: string) => void | undefined;
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,8 @@ export type StoreComponentResponse = {
|
||||||
authorized: boolean;
|
authorized: boolean;
|
||||||
results: storeComponent[];
|
results: storeComponent[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type shortcutsStoreType = {
|
||||||
|
openCodeModalWShortcut: boolean;
|
||||||
|
handleModalWShortcut: (str: string) => void;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
ArrowBigUp,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowUpToLine,
|
ArrowUpToLine,
|
||||||
Bell,
|
Bell,
|
||||||
|
|
@ -479,5 +480,6 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Bot,
|
Bot,
|
||||||
Delete,
|
Delete,
|
||||||
Command,
|
Command,
|
||||||
|
ArrowBigUp,
|
||||||
Dot,
|
Dot,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue