diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index 9296a4c64..91f68d711 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -27,12 +27,19 @@ def get_number_of_workers(workers=None): return workers -def update_settings(config: str, dev: bool = False, database_url: Optional[str] = None): +def update_settings( + config: str, + dev: bool = False, + database_url: Optional[str] = None, + remove_api_keys: bool = False, +): """Update the settings from a config file.""" if config: settings.update_from_yaml(config, dev=dev) if database_url: - settings.update_database_url(database_url) + settings.update_settings(database_url=database_url) + if remove_api_keys: + settings.update_settings(remove_api_keys=remove_api_keys) def serve_on_jcloud(): @@ -107,6 +114,9 @@ def serve( open_browser: bool = typer.Option( True, help="Open the browser after starting the server." ), + remove_api_keys: bool = typer.Option( + False, help="Remove API keys from the projects saved in the database." + ), ): """ Run the Langflow server. @@ -132,7 +142,9 @@ def serve( load_dotenv(env_file) configure(log_level=log_level, log_file=log_file) - update_settings(config, dev=dev, database_url=database_url) + update_settings( + config, dev=dev, database_url=database_url, remove_api_keys=remove_api_keys + ) app = create_app() # get the directory of the current file if not path: diff --git a/src/backend/langflow/api/utils.py b/src/backend/langflow/api/utils.py new file mode 100644 index 000000000..bfd9e3da5 --- /dev/null +++ b/src/backend/langflow/api/utils.py @@ -0,0 +1,24 @@ +API_WORDS = ["api", "key", "token"] + + +def has_api_terms(word: str): + return "api" in word and ( + "key" in word or ("token" in word and "tokens" not in word) + ) + + +def remove_api_keys(flow: dict): + """Remove api keys from flow data.""" + if flow.get("data") and flow["data"].get("nodes"): + for node in flow["data"]["nodes"]: + node_data = node.get("data").get("node") + template = node_data.get("template") + for value in template.values(): + if ( + isinstance(value, dict) + and has_api_terms(value["name"]) + and value.get("password") + ): + value["value"] = None + + return flow diff --git a/src/backend/langflow/api/v1/flows.py b/src/backend/langflow/api/v1/flows.py index b707f5e05..c440fc18b 100644 --- a/src/backend/langflow/api/v1/flows.py +++ b/src/backend/langflow/api/v1/flows.py @@ -1,5 +1,7 @@ from typing import List from uuid import UUID +from langflow.settings import settings +from langflow.api.utils import remove_api_keys from langflow.api.v1.schemas import FlowListCreate, FlowListRead from langflow.database.models.flow import ( Flow, @@ -54,10 +56,13 @@ def update_flow( *, session: Session = Depends(get_session), flow_id: UUID, flow: FlowUpdate ): """Update a flow.""" + db_flow = session.get(Flow, flow_id) if not db_flow: raise HTTPException(status_code=404, detail="Flow not found") flow_data = flow.dict(exclude_unset=True) + if not settings.remove_api_keys: + flow_data = remove_api_keys(flow_data) for key, value in flow_data.items(): setattr(db_flow, key, value) session.add(db_flow) diff --git a/src/backend/langflow/settings.py b/src/backend/langflow/settings.py index 1f304b67d..9d6ac3fa9 100644 --- a/src/backend/langflow/settings.py +++ b/src/backend/langflow/settings.py @@ -1,5 +1,5 @@ import os -from typing import List, Optional +from typing import List import yaml from pydantic import BaseSettings, root_validator @@ -21,6 +21,7 @@ class Settings(BaseSettings): utilities: List[str] = [] dev: bool = False database_url: str = "sqlite:///./langflow.db" + remove_api_keys: bool = False class Config: validate_assignment = True @@ -48,9 +49,10 @@ class Settings(BaseSettings): self.utilities = new_settings.utilities or [] self.dev = dev - def update_database_url(self, database_url: Optional[str] = None): - if database_url: - self.database_url = database_url + def update_settings(self, **kwargs): + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) def save_settings_to_yaml(settings: Settings, file_path: str): diff --git a/src/backend/langflow/template/frontend_node/llms.py b/src/backend/langflow/template/frontend_node/llms.py index d2af037dc..272e42c7f 100644 --- a/src/backend/langflow/template/frontend_node/llms.py +++ b/src/backend/langflow/template/frontend_node/llms.py @@ -46,7 +46,10 @@ class LLMFrontendNode(FrontendNode): if field.name in SHOW_FIELDS: field.show = True - if "api" in field.name and ("key" in field.name or "token" in field.name): + if "api" in field.name and ( + "key" in field.name + or ("token" in field.name and "tokens" not in field.name) + ): field.password = True field.show = True # Required should be False to support diff --git a/src/frontend/src/constants.tsx b/src/frontend/src/constants.tsx index 55a4123ce..e32336578 100644 --- a/src/frontend/src/constants.tsx +++ b/src/frontend/src/constants.tsx @@ -68,6 +68,8 @@ BASE_API_URL = "${window.location.protocol}//${ window.location.host }/ap1/v1/predict" FLOW_ID = "${flowId}" +# You can tweak the flow by adding a tweaks dictionary +# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} TWEAKS = ${JSON.stringify(tweaks, null, 2)} def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict: diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 4a88719b4..b62cca5af 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _, { set } from "lodash"; import { useContext, useRef, useState, useEffect, useCallback } from "react"; import ReactFlow, { OnSelectionChangeParams, @@ -15,6 +15,7 @@ import ReactFlow, { updateEdge, Background, Controls, + NodeChange, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; @@ -43,7 +44,9 @@ export default function Page({ flow }: { flow: FlowType }) { lastCopiedSelection, setLastCopiedSelection, tabsState, - saveFlow + saveFlow, + setTabsState, + tabId } = useContext(TabsContext); const { types, reactFlowInstance, setReactFlowInstance, templates } = useContext(typesContext); @@ -145,8 +148,31 @@ export default function Page({ flow }: { flow: FlowType }) { let newX = _.cloneDeep(x); return newX; }); + setTabsState((prev) => { + return { + ...prev, + [tabId]: { + isPending: true, + }, + }; + }); }, - [onEdgesChange, setNodes] + [onEdgesChange, setNodes,setTabsState,tabId] + ); + + const onNodesChangeMod = useCallback( + (s: NodeChange[]) => { + onNodesChange(s); + setTabsState((prev) => { + return { + ...prev, + [tabId]: { + isPending: true, + }, + }; + }); + }, + [onNodesChange,setTabsState,tabId] ); const onConnect = useCallback( @@ -345,7 +371,7 @@ export default function Page({ flow }: { flow: FlowType }) { onPaneMouseLeave={() => { setDisableCopyPaste(true); }} - onNodesChange={onNodesChange} + onNodesChange={onNodesChangeMod} onEdgesChange={onEdgesChangeMod} onConnect={onConnect} disableKeyboardA11y={true}