diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 208007394..c05e76325 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -101,12 +101,8 @@ async def build_vertex( cache = chat_service.get_cache(flow_id) if not cache: # If there's no cache - logger.warning( - 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 - ) + logger.warning(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) else: graph = cache.get("result") result_data_response = ResultDataResponse(results={}) @@ -211,9 +207,7 @@ async def build_vertex_stream( else: graph = cache.get("result") else: - session_data = await session_service.load_session( - session_id, flow_id=flow_id - ) + session_data = await session_service.load_session(session_id, flow_id=flow_id) graph, artifacts = session_data if session_data else (None, None) if not graph: raise ValueError(f"No graph found for {flow_id}.") diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index 2c06ebe27..0e0c13802 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -264,9 +264,7 @@ class Graph: def build_parent_child_map(self): parent_child_map = defaultdict(list) for vertex in self.vertices: - parent_child_map[vertex.id] = [ - child.id for child in self.get_successors(vertex) - ] + parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)] return parent_child_map def increment_run_count(self): @@ -480,11 +478,7 @@ class Graph: return self.vertices.remove(vertex) self.vertex_map.pop(vertex_id) - self.edges = [ - edge - for edge in self.edges - if edge.source_id != vertex_id and edge.target_id != vertex_id - ] + self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id] def _build_vertex_params(self) -> None: """Identifies and handles the LLM vertex within the graph.""" @@ -505,9 +499,7 @@ class Graph: return for vertex in self.vertices: if not self._validate_vertex(vertex): - raise ValueError( - f"{vertex.display_name} is not connected to any other components" - ) + raise ValueError(f"{vertex.display_name} is not connected to any other components") def _validate_vertex(self, vertex: Vertex) -> bool: """Validates a vertex.""" @@ -613,9 +605,7 @@ class Graph: def dfs(vertex): if state[vertex] == 1: # We have a cycle - raise ValueError( - "Graph contains a cycle, cannot perform topological sort" - ) + raise ValueError("Graph contains a cycle, cannot perform topological sort") if state[vertex] == 0: state[vertex] = 1 for edge in vertex.edges: @@ -639,10 +629,7 @@ class Graph: def get_predecessors(self, vertex): """Returns the predecessors of a vertex.""" - return [ - self.get_vertex(source_id) - for source_id in self.predecessor_map.get(vertex.id, []) - ] + return [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): # Recursively get the successors of the current vertex @@ -683,10 +670,7 @@ class Graph: def get_successors(self, vertex): """Returns the successors of a vertex.""" - return [ - self.get_vertex(target_id) - for target_id in self.successor_map.get(vertex.id, []) - ] + return [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]: """Returns the neighbors of a vertex.""" @@ -732,9 +716,7 @@ class Graph: edges_added.add((source.id, target.id)) return edges - def _get_vertex_class( - self, node_type: str, node_base_type: str, node_id: str - ) -> Type[Vertex]: + def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]: """Returns the node class based on the node type.""" # First we check for the node_base_type node_name = node_id.split("-")[0] @@ -767,18 +749,14 @@ class Graph: vertex_type: str = vertex_data["type"] # type: ignore vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore - VertexClass = self._get_vertex_class( - vertex_type, vertex_base_type, vertex_data["id"] - ) + VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"]) vertex_instance = VertexClass(vertex, graph=self) vertex_instance.set_top_level(self.top_level_vertices) vertices.append(vertex_instance) return vertices - def get_children_by_vertex_type( - self, vertex: Vertex, vertex_type: str - ) -> List[Vertex]: + def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]: """Returns the children of a vertex based on the vertex type.""" children = [] vertex_types = [vertex.data["type"]] @@ -790,9 +768,7 @@ class Graph: def __repr__(self): vertex_ids = [vertex.id for vertex in self.vertices] - edges_repr = "\n".join( - [f"{edge.source_id} --> {edge.target_id}" for edge in self.edges] - ) + edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]) return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}" def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]: @@ -935,9 +911,7 @@ class Graph: return refined_layers - def sort_chat_inputs_first( - self, vertices_layers: List[List[str]] - ) -> List[List[str]]: + def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]: chat_inputs_first = [] for layer in vertices_layers: for vertex_id in layer: @@ -991,15 +965,11 @@ class Graph: self.vertices_to_run.remove(vertex_id) return should_run - def sort_interface_components_first( - self, vertices_layers: List[List[str]] - ) -> List[List[str]]: + def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]: """Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first.""" def contains_interface_component(vertex): - return any( - component.value in vertex for component in InterfaceComponentTypes - ) + return any(component.value in vertex for component in InterfaceComponentTypes) # Sort each inner list so that vertices containing ChatInput or ChatOutput come first sorted_vertices = [ @@ -1011,22 +981,16 @@ class Graph: ] return sorted_vertices - def sort_by_avg_build_time( - self, vertices_layers: List[List[str]] - ) -> List[List[str]]: + def sort_by_avg_build_time(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.""" 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.""" if len(vertices_ids) == 1: return vertices_ids - vertices_ids.sort( - key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time - ) + vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time) return vertices_ids - sorted_vertices = [ - sort_layer_by_avg_build_time(layer) for layer in vertices_layers - ] + sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers] return sorted_vertices diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index 3fa59ab45..201dc77c0 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -106,10 +106,7 @@ class Vertex: def set_state(self, state: str): self.state = VertexStates[state] - if ( - self.state == VertexStates.INACTIVE - and self.graph.in_degree_map[self.id] < 2 - ): + if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2: # If the vertex is inactive and has only one in degree # it means that it is not a merge point in the graph self.graph.inactivated_vertices.add(self.id) @@ -133,9 +130,7 @@ class Vertex: # If the Vertex.type is a power component # then we need to return the built object # instead of the result dict - if self.is_interface_component and not isinstance( - self._built_object, UnbuiltObject - ): + if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject): result = self._built_object # if it is not a dict or a string and hasattr model_dump then # return the model_dump @@ -147,11 +142,7 @@ class Vertex: if isinstance(self._built_result, UnbuiltResult): return {} - return ( - self._built_result - if isinstance(self._built_result, dict) - else {"result": self._built_result} - ) + return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result} def set_artifacts(self) -> None: pass @@ -221,31 +212,19 @@ class Vertex: 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_output = self.data["node"].get("is_output") or self.is_output - template_dicts = { - key: value - for key, value in self.data["node"]["template"].items() - if isinstance(value, dict) - } + template_dicts = {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.required_inputs = [ - template_dicts[key]["type"] - for key, value in template_dicts.items() - if value["required"] + template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"] ] self.optional_inputs = [ - template_dicts[key]["type"] - for key, value in template_dicts.items() - if not value["required"] + template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"] ] # Add the template_dicts[key]["input_types"] to the optional_inputs 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"] @@ -292,11 +271,7 @@ class Vertex: self.updated_raw_params = False return - template_dict = { - key: value - for key, value in self.data["node"]["template"].items() - if isinstance(value, dict) - } + template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)} params = {} for edge in self.edges: @@ -356,11 +331,7 @@ class Vertex: # list of dicts, so we need to convert it to a dict # before passing it to the build method if isinstance(val, list): - params[key] = { - k: v - for item in value.get("value", []) - for k, v in item.items() - } + params[key] = {k: v for item in value.get("value", []) for k, v in item.items()} elif isinstance(val, dict): params[key] = val elif value.get("type") == "int" and val is not None: @@ -485,9 +456,7 @@ class Vertex: if isinstance(self._built_object, str): self._built_result = self._built_object - result = await generate_result( - self._built_object, inputs, self.has_external_output, session_id - ) + result = await generate_result(self._built_object, inputs, self.has_external_output, session_id) self._built_result = result 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) - async def get_result( - self, requester: Optional["Vertex"] = None, user_id=None, timeout=None - ) -> Any: + async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any: # PLEASE REVIEW THIS IF STATEMENT # Check if the Vertex was built already if self._built: @@ -568,9 +535,7 @@ class Vertex: self._extend_params_list_with_result(key, result) self.params[key] = result - async def _build_list_of_nodes_and_update_params( - self, key, nodes: List["Vertex"], user_id=None - ): + async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None): """ Iterates over a list of nodes, builds each and updates the params dictionary. """ @@ -637,9 +602,7 @@ class Vertex: except Exception as exc: logger.exception(exc) - raise ValueError( - f"Error building node {self.display_name}: {str(exc)}" - ) from exc + raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc def _update_built_object_and_artifacts(self, result): """ @@ -732,24 +695,16 @@ class Vertex: return self._built_object # Get the requester edge - requester_edge = next( - (edge for edge in self.edges if edge.target_id == requester.id), None - ) + requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None) # Return the result of the requester edge - return ( - None - if requester_edge is None - else await requester_edge.get_result(source=self, target=requester) - ) + return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester) def add_edge(self, edge: "ContractEdge") -> None: if edge not in self.edges: self.edges.append(edge) def __repr__(self) -> str: - return ( - f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})" - ) + return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})" def __eq__(self, __o: object) -> bool: try: diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 3a4df9f56..dd57a8b25 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -43,9 +43,7 @@ def add_output_types( raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid return type. Please check your code and try again." - ), + "error": ("Invalid return type. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) @@ -77,18 +75,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List frontend_node.field_order = field_order -def add_base_classes( - frontend_node: CustomComponentFrontendNode, return_types: List[str] -): +def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]): """Add base classes to the frontend node""" for return_type_instance in return_types: if return_type_instance is None: raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid return type. Please check your code and try again." - ), + "error": ("Invalid return type. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) @@ -176,9 +170,7 @@ def add_new_custom_field( ) if "name" in field_config: - warnings.warn( - "The 'name' key in field_config is used to build the object and can't be changed." - ) + warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.") required = field_config.pop("required", field_required) placeholder = field_config.pop("placeholder", "") @@ -277,9 +269,7 @@ def run_build_config( raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid type convertion. Please check your code and try again." - ), + "error": ("Invalid type convertion. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) from exc @@ -397,16 +387,10 @@ def build_custom_component_template( add_extra_fields(frontend_node, field_config, entrypoint_args) - frontend_node = add_code_field( - frontend_node, custom_component.code, field_config.get("code", {}) - ) + frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {})) - add_base_classes( - frontend_node, custom_component.get_function_entrypoint_return_type - ) - add_output_types( - frontend_node, custom_component.get_function_entrypoint_return_type - ) + add_base_classes(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()) @@ -455,9 +439,7 @@ def build_custom_components(components_paths: List[str]): custom_component_dict = build_custom_component_list_from_path(path_str) if custom_component_dict: category = next(iter(custom_component_dict)) - logger.info( - f"Loading {len(custom_component_dict[category])} component(s) from category {category}" - ) + logger.info(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, custom_component_dict ) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 4b0b752e1..e4a48eddb 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -159,7 +159,9 @@ export default function GenericNode({ } }, [validationStatus, validationStatus?.params]); - const showNode = data.showNode ?? true; + // const showNode = data.showNode ?? true; + + const [showNode, setShowNode] = useState(data.showNode ?? true); const nameEditable = true; @@ -298,6 +300,7 @@ export default function GenericNode({ data: { ...old.data, showNode: show }, })); }} + setShowState={setShowNode} numberOfHandles={handles} showNode={showNode} openAdvancedModal={false} diff --git a/src/frontend/src/assets/undraw_mobile_messages_re_yx8w.svg b/src/frontend/src/assets/undraw_mobile_messages_re_yx8w.svg new file mode 100644 index 000000000..f232003d2 --- /dev/null +++ b/src/frontend/src/assets/undraw_mobile_messages_re_yx8w.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/assets/undraw_real_time_analytics_re_yliv.svg b/src/frontend/src/assets/undraw_real_time_analytics_re_yliv.svg new file mode 100644 index 000000000..32873b55f --- /dev/null +++ b/src/frontend/src/assets/undraw_real_time_analytics_re_yliv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/assets/undraw_team_collaboration_re_ow29.svg b/src/frontend/src/assets/undraw_team_collaboration_re_ow29.svg new file mode 100644 index 000000000..38bc4b3d5 --- /dev/null +++ b/src/frontend/src/assets/undraw_team_collaboration_re_ow29.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index b2f1bc3e2..6db42aacc 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -135,6 +135,15 @@ export const MenuBar = ({ > Undo + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + + )} + Z { @@ -144,6 +153,15 @@ export const MenuBar = ({ > Redo + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + + )} + Y diff --git a/src/frontend/src/components/ui/select-custom.tsx b/src/frontend/src/components/ui/select-custom.tsx index 5ed2fbd45..8c5223231 100644 --- a/src/frontend/src/components/ui/select-custom.tsx +++ b/src/frontend/src/components/ui/select-custom.tsx @@ -33,7 +33,7 @@ const SelectContent = React.forwardRef< case "Basic Prompting": return - default: + case "Chat with memory": + return + case "API requests": + return + case "Assistant": + return + default: return } } diff --git a/src/frontend/src/modals/ConfirmationModal/index.tsx b/src/frontend/src/modals/ConfirmationModal/index.tsx index 11e6e6edf..2c6377a12 100644 --- a/src/frontend/src/modals/ConfirmationModal/index.tsx +++ b/src/frontend/src/modals/ConfirmationModal/index.tsx @@ -61,7 +61,7 @@ function ConfirmationModal({ ); return ( - + {triggerChild} {title} diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index 2417724b4..3705f5530 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -46,13 +46,13 @@ export default function CodeAreaModal({ : useState(false); const dark = useDarkStore((state) => state.dark); const unselectAll = useFlowStore((state) => state.unselectAll); - const [height, setHeight] = useState(null); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const [error, setError] = useState<{ detail: CodeErrorDataTypeAPI; } | null>(null); + const nodes = useFlowStore((state) => state.nodes); useEffect(() => { // if nodeClass.template has more fields other than code and dynamic is true diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 694497987..637779cc1 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -27,6 +27,7 @@ import { updateFlowPosition, } from "../../../../utils/reactflowUtils"; import { classNames, cn } from "../../../../utils/utils"; +import useAlertStore from "../../../../stores/alertStore"; export default function NodeToolbarComponent({ data, @@ -37,6 +38,7 @@ export default function NodeToolbarComponent({ showNode, name = "code", selected, + setShowState, onCloseAdvancedModal, }: nodeToolbarPropsType): JSX.Element { const nodeLength = Object.keys(data.node!.template).filter( @@ -58,6 +60,7 @@ export default function NodeToolbarComponent({ const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); const validApiKey = useStoreStore((state) => state.validApiKey); + const [updateMinimize, setUpdateMinimize] = useState(showNode); const isMinimal = numberOfHandles <= 1; const isGroup = data.node?.flow ? true : false; @@ -68,7 +71,7 @@ export default function NodeToolbarComponent({ const edges = useFlowStore((state) => state.edges); const setNodes = useFlowStore((state) => state.setNodes); const setEdges = useFlowStore((state) => state.setEdges); - + const unselectAll = useFlowStore((state) => state.unselectAll); const saveComponent = useFlowsManagerStore((state) => state.saveComponent); const flows = useFlowsManagerStore((state) => state.flows); const version = useDarkStore((state) => state.version); @@ -93,6 +96,10 @@ export default function NodeToolbarComponent({ const setLastCopiedSelection = useFlowStore( (state) => state.setLastCopiedSelection ); + + const setSuccessData = useAlertStore(state => state.setSuccessData); + const setNoticeData = useAlertStore(state => state.setNoticeData); + useEffect(() => { setFlowComponent(createFlowComponent(cloneDeep(data), version)); }, [ @@ -222,7 +229,105 @@ export default function NodeToolbarComponent({ const [openModal, setOpenModal] = useState(false); 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 ( <>
@@ -244,7 +349,7 @@ export default function NodeToolbarComponent({
{hasStore && ( @@ -352,6 +479,20 @@ export default function NodeToolbarComponent({ className="relative top-0.5 -m-1 mr-1 h-6 w-6" />{" "} Share{" "} + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + )} + + + S {" "} )} @@ -373,10 +514,23 @@ export default function NodeToolbarComponent({
{" "} - Docs -
{" "} + Docs{" "} + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + )} + + D + {isMinimal && ( @@ -386,6 +540,15 @@ export default function NodeToolbarComponent({ className="relative top-0.5 mr-2 h-4 w-4" /> {showNode ? "Minimize" : "Expand"} + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + + )} + Q )} @@ -393,10 +556,19 @@ export default function NodeToolbarComponent({
{" "} - Ungroup{" "} + Ungroup{" "} + {navigator.userAgent.toUpperCase().includes("MAC") ? ( + + ) : ( + Ctrl + + )} + U
)} @@ -429,9 +601,13 @@ export default function NodeToolbarComponent({ index={6} onConfirm={(index, user) => { saveComponent(cloneDeep(data), true); + setSuccessData({title: `${data.id} successfully overridden!`}) }} onClose={setShowOverrideModal} - onCancel={() => saveComponent(cloneDeep(data), false)} + onCancel={() => { + saveComponent(cloneDeep(data), false) + setSuccessData({title: "New node successfully saved!"}) + }} > diff --git a/src/frontend/src/stores/shortcuts.ts b/src/frontend/src/stores/shortcuts.ts new file mode 100644 index 000000000..798613708 --- /dev/null +++ b/src/frontend/src/stores/shortcuts.ts @@ -0,0 +1,15 @@ +import { create } from "zustand"; +import { shortcutsStoreType } from "../types/store"; + +export const useShortcutsStore = create((set, get) => ({ + openCodeModalWShortcut: false, + handleModalWShortcut: (modal) => { + switch (modal) { + case "code": + set({ + openCodeModalWShortcut: !get().openCodeModalWShortcut, + }); + break; + } + }, +})); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 14fb959ff..78333add9 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode } from "react"; +import { ReactElement, ReactNode, SetStateAction } from "react"; import { ReactFlowJsonObject, XYPosition } from "reactflow"; import { APIClassType, APITemplateType, TemplateVariableType } from "../api"; import { ChatMessageType } from "../chat"; @@ -107,6 +107,7 @@ export type PromptAreaComponentType = { }; export type CodeAreaComponentType = { + setOpenModal?: (bool: boolean) => void; disabled: boolean; onChange: (value: string[] | string) => void; value: string; @@ -490,6 +491,7 @@ export type nodeToolbarPropsType = { openAdvancedModal?: boolean; onCloseAdvancedModal?: (close: boolean) => void; selected: boolean; + setShowState: (show: boolean | SetStateAction) => void; }; export type parsedDataType = { @@ -517,6 +519,7 @@ export type modalHeaderType = { export type codeAreaModalPropsType = { setValue: (value: string) => void; + setOpenModal?: (bool: boolean) => void; value: string; nodeClass: APIClassType | undefined; setNodeClass: (Class: APIClassType, code?: string) => void | undefined; diff --git a/src/frontend/src/types/store/index.ts b/src/frontend/src/types/store/index.ts index 331e16e30..b116bbc3d 100644 --- a/src/frontend/src/types/store/index.ts +++ b/src/frontend/src/types/store/index.ts @@ -18,3 +18,8 @@ export type StoreComponentResponse = { authorized: boolean; results: storeComponent[]; }; + +export type shortcutsStoreType = { + openCodeModalWShortcut: boolean; + handleModalWShortcut: (str: string) => void; +}; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 7ebcda3c2..f2d28fa28 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -1,5 +1,6 @@ import { AlertCircle, + ArrowBigUp, ArrowLeft, ArrowUpToLine, Bell, @@ -479,5 +480,6 @@ export const nodeIconsLucide: iconsType = { Bot, Delete, Command, + ArrowBigUp, Dot, };