diff --git a/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py b/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py new file mode 100644 index 000000000..e47d68702 --- /dev/null +++ b/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py @@ -0,0 +1,43 @@ +"""Add default_fields column + +Revision ID: 1f4d6df60295 +Revises: 6e7b581b5648 +Create Date: 2024-04-29 09:49:46.864145 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.engine.reflection import Inspector + +# revision identifiers, used by Alembic. +revision: str = "1f4d6df60295" +down_revision: Union[str, None] = "6e7b581b5648" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + # ### commands auto generated by Alembic - please adjust! ### + column_names = [column["name"] for column in inspector.get_columns("variable")] + with op.batch_alter_table("variable", schema=None) as batch_op: + if "default_fields" not in column_names: + batch_op.add_column(sa.Column("default_fields", sa.JSON(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + # ### commands auto generated by Alembic - please adjust! ### + column_names = [column["name"] for column in inspector.get_columns("variable")] + with op.batch_alter_table("variable", schema=None) as batch_op: + if "default_fields" in column_names: + batch_op.drop_column("default_fields") + + # ### end Alembic commands ### diff --git a/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py b/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py new file mode 100644 index 000000000..cec3f6081 --- /dev/null +++ b/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py @@ -0,0 +1,52 @@ +"""Set name and value to not nullable + +Revision ID: c153816fd85f +Revises: 1f4d6df60295 +Create Date: 2024-04-30 14:31:23.898995 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.engine.reflection import Inspector + +# revision identifiers, used by Alembic. +revision: str = "c153816fd85f" +down_revision: Union[str, None] = "1f4d6df60295" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + # ### commands auto generated by Alembic - please adjust! ### + columns = inspector.get_columns("variable") + with op.batch_alter_table("variable", schema=None) as batch_op: + name_column = [column for column in columns if column["name"] == "name"][0] + if name_column and name_column["nullable"]: + batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=False) + value_column = [column for column in columns if column["name"] == "value"][0] + if value_column and value_column["nullable"]: + batch_op.alter_column("value", existing_type=sa.VARCHAR(), nullable=False) + + +# ### end Alembic commands ### + + +def downgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + columns = inspector.get_columns("variable") + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("variable", schema=None) as batch_op: + name_column = [column for column in columns if column["name"] == "name"][0] + if name_column and not name_column["nullable"]: + batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True) + value_column = [column for column in columns if column["name"] == "value"][0] + if value_column and not value_column["nullable"]: + batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True) + + # ### end Alembic commands ### diff --git a/src/backend/base/langflow/api/v1/variable.py b/src/backend/base/langflow/api/v1/variable.py index 123086676..f992c1429 100644 --- a/src/backend/base/langflow/api/v1/variable.py +++ b/src/backend/base/langflow/api/v1/variable.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from uuid import UUID from fastapi import APIRouter, Depends, HTTPException @@ -37,7 +37,11 @@ def create_variable( variable_dict["user_id"] = current_user.id db_variable = Variable.model_validate(variable_dict) - if not db_variable.value: + if not db_variable.name and not db_variable.value: + raise HTTPException(status_code=400, detail="Variable name and value cannot be empty") + elif not db_variable.name: + raise HTTPException(status_code=400, detail="Variable name cannot be empty") + elif not db_variable.value: raise HTTPException(status_code=400, detail="Variable value cannot be empty") encrypted = auth_utils.encrypt_api_key(db_variable.value, settings_service=settings_service) db_variable.value = encrypted @@ -85,7 +89,7 @@ def update_variable( variable_data = variable.model_dump(exclude_unset=True) for key, value in variable_data.items(): setattr(db_variable, key, value) - db_variable.updated_at = datetime.utcnow() + db_variable.updated_at = datetime.now(timezone.utc) session.commit() session.refresh(db_variable) return db_variable diff --git a/src/backend/base/langflow/components/models/AzureOpenAIModel.py b/src/backend/base/langflow/components/models/AzureOpenAIModel.py index b6d3607f3..a2ee20b28 100644 --- a/src/backend/base/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/base/langflow/components/models/AzureOpenAIModel.py @@ -1,6 +1,5 @@ from typing import Optional -from langchain.llms.base import BaseLanguageModel from langchain_openai import AzureChatOpenAI from pydantic.v1 import SecretStr diff --git a/src/backend/base/langflow/components/models/ChatLiteLLMModel.py b/src/backend/base/langflow/components/models/ChatLiteLLMModel.py index 23ac483cb..95574f0a5 100644 --- a/src/backend/base/langflow/components/models/ChatLiteLLMModel.py +++ b/src/backend/base/langflow/components/models/ChatLiteLLMModel.py @@ -4,7 +4,7 @@ from langchain_community.chat_models.litellm import ChatLiteLLM, ChatLiteLLMExce from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent -from langflow.field_typing import BaseLanguageModel, Text +from langflow.field_typing import Text class ChatLiteLLMModelComponent(LCModelComponent): diff --git a/src/backend/base/langflow/schema/schema.py b/src/backend/base/langflow/schema/schema.py index 1474c24ee..29ab15ede 100644 --- a/src/backend/base/langflow/schema/schema.py +++ b/src/backend/base/langflow/schema/schema.py @@ -4,6 +4,7 @@ from typing import Literal, Optional, cast from langchain_core.documents import Document from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from pydantic import BaseModel, model_validator +from langchain_core.messages import HumanMessage, AIMessage class Record(BaseModel): diff --git a/src/backend/base/langflow/services/database/models/variable/model.py b/src/backend/base/langflow/services/database/models/variable/model.py index e253bb665..1344dc9c3 100644 --- a/src/backend/base/langflow/services/database/models/variable/model.py +++ b/src/backend/base/langflow/services/database/models/variable/model.py @@ -1,8 +1,8 @@ from datetime import datetime, timezone -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, List, Optional from uuid import UUID, uuid4 -from sqlmodel import Column, DateTime, Field, Relationship, SQLModel, func +from sqlmodel import JSON, Column, DateTime, Field, Relationship, SQLModel, func if TYPE_CHECKING: from langflow.services.database.models.user.model import User @@ -13,8 +13,9 @@ def utc_now(): class VariableBase(SQLModel): - name: Optional[str] = Field(None, description="Name of the variable") - value: Optional[str] = Field(None, description="Encrypted value of the variable") + name: str = Field(description="Name of the variable") + value: str = Field(description="Encrypted value of the variable") + default_fields: Optional[List[str]] = Field(sa_column=Column(JSON)) type: Optional[str] = Field(None, description="Type of the variable") @@ -35,6 +36,7 @@ class Variable(VariableBase, table=True): sa_column=Column(DateTime(timezone=True), nullable=True), description="Last update time of the variable", ) + default_fields: Optional[List[str]] = Field(sa_column=Column(JSON)) # foreign key to user table user_id: UUID = Field(description="User ID associated with this variable", foreign_key="user.id") user: "User" = Relationship(back_populates="variables") @@ -50,9 +52,11 @@ class VariableRead(SQLModel): id: UUID name: Optional[str] = Field(None, description="Name of the variable") type: Optional[str] = Field(None, description="Type of the variable") + default_fields: Optional[List[str]] = Field(None, description="Default fields for the variable") class VariableUpdate(SQLModel): id: UUID # Include the ID for updating name: Optional[str] = Field(None, description="Name of the variable") value: Optional[str] = Field(None, description="Encrypted value of the variable") + default_fields: Optional[List[str]] = Field(None, description="Default fields for the variable") diff --git a/src/backend/base/langflow/services/variable/service.py b/src/backend/base/langflow/services/variable/service.py index ef23a95a2..ea5126d79 100644 --- a/src/backend/base/langflow/services/variable/service.py +++ b/src/backend/base/langflow/services/variable/service.py @@ -30,7 +30,14 @@ class VariableService(Service): if var in os.environ: logger.debug(f"Creating {var} variable from environment.") try: - self.create_variable(user_id, var, os.environ[var], _type="Credential", session=session) + self.create_variable( + user_id=user_id, + name=var, + value=os.environ[var], + default_fields=[], + _type="Credential", + session=session, + ) except Exception as e: logger.error(f"Error creating {var} variable: {e}") @@ -91,6 +98,7 @@ class VariableService(Service): user_id: Union[UUID, str], name: str, value: str, + default_fields: list[str] = [], _type: str = "Generic", session: Session = Depends(get_session), ): @@ -98,6 +106,7 @@ class VariableService(Service): name=name, type=_type, value=auth_utils.encrypt_api_key(value, settings_service=self.settings_service), + default_fields=default_fields, ) variable = Variable.model_validate(variable_base, from_attributes=True, update={"user_id": user_id}) session.add(variable) diff --git a/src/frontend/.prettierignore b/src/frontend/.prettierignore new file mode 100644 index 000000000..07ed7069a --- /dev/null +++ b/src/frontend/.prettierignore @@ -0,0 +1 @@ +build/* \ No newline at end of file diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 686da17f1..ae8bdb098 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -31,6 +31,7 @@ "@tailwindcss/line-clamp": "^0.4.4", "@types/axios": "^0.14.0", "ace-builds": "^1.24.1", + "ag-grid-community": "^31.2.1", "ag-grid-react": "^31.2.1", "ansi-to-html": "^0.7.2", "axios": "^1.5.0", @@ -49,10 +50,10 @@ "moment": "^2.29.4", "openseadragon": "^4.1.1", "playwright": "^1.42.0", - "react": "^18.2.0", + "react": "^18.2.21", "react-ace": "^10.1.0", "react-cookie": "^4.1.1", - "react-dom": "^18.2.0", + "react-dom": "^18.2.21", "react-error-boundary": "^4.0.11", "react-icons": "^5.0.1", "react-laag": "^2.0.5", diff --git a/src/frontend/package.json b/src/frontend/package.json index 3a6a2a8a9..ddef9be04 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -26,6 +26,7 @@ "@tailwindcss/line-clamp": "^0.4.4", "@types/axios": "^0.14.0", "ace-builds": "^1.24.1", + "ag-grid-community": "^31.2.1", "ag-grid-react": "^31.2.1", "ansi-to-html": "^0.7.2", "axios": "^1.5.0", @@ -44,10 +45,10 @@ "moment": "^2.29.4", "openseadragon": "^4.1.1", "playwright": "^1.42.0", - "react": "^18.2.0", + "react": "^18.2.21", "react-ace": "^10.1.0", "react-cookie": "^4.1.1", - "react-dom": "^18.2.0", + "react-dom": "^18.2.21", "react-error-boundary": "^4.0.11", "react-icons": "^5.0.1", "react-laag": "^2.0.5", @@ -74,7 +75,7 @@ "start": "vite", "build": "vite build", "serve": "vite preview", - "format": "npx prettier --write \"./**/*.{js,jsx,ts,tsx,json,md}\"", + "format": "npx prettier --write \"./**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore", "type-check": "tsc --noEmit --pretty --project tsconfig.json && vite" }, "eslintConfig": { diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 4a551f3b5..1a04e08f7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -22,7 +22,6 @@ import useFlowsManagerStore from "./stores/flowsManagerStore"; import { useGlobalVariablesStore } from "./stores/globalVariables"; import { useStoreStore } from "./stores/storeStore"; import { useTypesStore } from "./stores/typesStore"; - export default function App() { const removeFromTempNotificationList = useAlertStore( (state) => state.removeFromTempNotificationList @@ -48,6 +47,9 @@ export default function App() { const setGlobalVariables = useGlobalVariablesStore( (state) => state.setGlobalVariables ); + const setUnavailableFields = useGlobalVariablesStore( + (state) => state.setUnavaliableFields + ); const checkHasStore = useStoreStore((state) => state.checkHasStore); const navigate = useNavigate(); const dark = useDarkStore((state) => state.dark); diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx index 8dab6420f..e5e8dd488 100644 --- a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx +++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx @@ -3,6 +3,7 @@ import { registerGlobalVariable } from "../../controllers/API"; import BaseModal from "../../modals/baseModal"; import useAlertStore from "../../stores/alertStore"; import { useGlobalVariablesStore } from "../../stores/globalVariables"; +import { useTypesStore } from "../../stores/typesStore"; import { ResponseErrorDetailAPI } from "../../types/api"; import ForwardedIconComponent from "../genericIconComponent"; import InputComponent from "../inputComponent"; @@ -16,25 +17,42 @@ import { Textarea } from "../ui/textarea"; export default function AddNewVariableButton({ children }): JSX.Element { const [key, setKey] = useState(""); const [value, setValue] = useState(""); - const [type, setType] = useState(""); + const [type, setType] = useState("Generic"); + const [fields, setFields] = useState([]); const [open, setOpen] = useState(false); const setErrorData = useAlertStore((state) => state.setErrorData); + const componentFields = useTypesStore((state) => state.ComponentFields); + const unavaliableFields =new Set(Object.keys(useGlobalVariablesStore( + (state) => state.unavaliableFields + ))); + + const availableFields = Array.from(componentFields).filter( + (field) => !unavaliableFields.has(field) + ); const addGlobalVariable = useGlobalVariablesStore( (state) => state.addGlobalVariable ); + function handleSaveVariable() { - let data: { name: string; value: string; type?: string } = { + let data: { + name: string; + value: string; + type?: string; + default_fields?: string[]; + } = { name: key, type, value, + default_fields: fields, }; registerGlobalVariable(data) .then((res) => { const { name, id, type } = res.data; - addGlobalVariable(name, id, type); + addGlobalVariable(name, id, type, fields); setKey(""); setValue(""); setType(""); + setFields([]); setOpen(false); }) .catch((error) => { @@ -89,6 +107,14 @@ export default function AddNewVariableButton({ children }): JSX.Element { placeholder="Insert a value for the variable..." className="w-full resize-none custom-scroll" /> + + setFields(value)} + selectedOptions={fields} + password={false} + options={availableFields} + placeholder="Choose a field for the variable..." + > diff --git a/src/frontend/src/components/dropdownComponent/index.tsx b/src/frontend/src/components/dropdownComponent/index.tsx index 67b9c5d82..b17332268 100644 --- a/src/frontend/src/components/dropdownComponent/index.tsx +++ b/src/frontend/src/components/dropdownComponent/index.tsx @@ -1,3 +1,4 @@ +import { PopoverAnchor } from "@radix-ui/react-popover"; import { useRef, useState } from "react"; import { DropDownComponentType } from "../../types/components"; import { cn } from "../../utils/utils"; @@ -13,6 +14,7 @@ import { } from "../ui/command"; import { Popover, + PopoverContent, PopoverContentWithoutPortal, PopoverTrigger, } from "../ui/popover"; @@ -25,50 +27,63 @@ export default function Dropdown({ onSelect, editNode = false, id = "", + children, }: DropDownComponentType): JSX.Element { - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(children ? true : false); const refButton = useRef(null); + const PopoverContentDropdown = children + ? PopoverContent + : PopoverContentWithoutPortal; + return ( <> {Object.keys(options)?.length > 0 ? ( <> - - - - - + + + )} + @@ -98,7 +113,7 @@ export default function Dropdown({ - + ) : ( diff --git a/src/frontend/src/components/gradientChooserComponent/index.tsx b/src/frontend/src/components/gradientChooserComponent/index.tsx index bff1fbd51..3467aedb8 100644 --- a/src/frontend/src/components/gradientChooserComponent/index.tsx +++ b/src/frontend/src/components/gradientChooserComponent/index.tsx @@ -2,7 +2,7 @@ import { gradients } from "../../utils/styleUtils"; export default function GradientChooserComponent({ value, onChange }) { return ( -
+
{gradients.map((gradient, idx) => (
{ diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 2973fe61a..af219f2ac 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -168,50 +168,56 @@ export default function Header(): JSX.Element { /> )} - {!autoLogin && ( - <> - - - -
diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx index bd7f852ab..e355293ff 100644 --- a/src/frontend/src/components/inputComponent/index.tsx +++ b/src/frontend/src/components/inputComponent/index.tsx @@ -33,6 +33,8 @@ export default function InputComponent({ optionsIcon = "ChevronsUpDown", selectedOption, setSelectedOption, + selectedOptions = [], + setSelectedOptions, options = [], optionsPlaceholder = "Search options...", optionsButton, @@ -101,13 +103,18 @@ export default function InputComponent({ value={ (selectedOption !== "" || !onChange) && setSelectedOption ? selectedOption + : (selectedOptions?.length !== 0 || !onChange) && + setSelectedOptions + ? selectedOptions?.join(", ") : value } autoFocus={autoFocus} disabled={disabled} onClick={() => { - (selectedOption !== "" || !onChange) && - setSelectedOption && + (((selectedOption !== "" || !onChange) && + setSelectedOption) || + ((selectedOptions?.length !== 0 || !onChange) && + setSelectedOptions)) && setShowOptions(true); }} required={required} @@ -119,9 +126,11 @@ export default function InputComponent({ ? " text-clip password " : "", editNode ? " input-edit-node " : "", - password && setSelectedOption ? "pr-[62.9px]" : "", - (!password && setSelectedOption) || - (password && !setSelectedOption) + password && (setSelectedOption || setSelectedOptions) + ? "pr-[62.9px]" + : "", + (!password && (setSelectedOption || setSelectedOptions)) || + (password && !(setSelectedOption || setSelectedOptions)) ? "pr-8" : "", @@ -164,7 +173,10 @@ export default function InputComponent({ > { - if (value.includes(search) || value.includes("doNotFilter-")) + if ( + value.toLowerCase().includes(search.toLowerCase()) || + value.includes("doNotFilter-") + ) return 1; // ensures items arent filtered return 0; }} @@ -184,7 +196,15 @@ export default function InputComponent({ ? "" : currentValue ); - setShowOptions(false); + setSelectedOptions && + setSelectedOptions( + selectedOptions?.includes(currentValue) + ? selectedOptions.filter( + (item) => item !== currentValue + ) + : [...selectedOptions, currentValue] + ); + !setSelectedOptions && setShowOptions(false); }} >
@@ -192,7 +212,8 @@ export default function InputComponent({
{ setShowOptions((old) => !old); e.preventDefault(); @@ -245,7 +270,7 @@ export default function InputComponent({ )} - {setSelectedOption && ( + {(setSelectedOption || setSelectedOptions) && ( state.getVariableId); + const unavaliableFields = useGlobalVariablesStore( + (state) => state.unavaliableFields + ); const removeGlobalVariable = useGlobalVariablesStore( (state) => state.removeGlobalVariable ); @@ -35,16 +37,32 @@ export default function InputGlobalComponent({ !globalVariablesEntries.includes(data.node?.template[name].value) && data.node?.template[name].load_from_db ) { - onChange(""); - setDb(false); + setTimeout(() => { + onChange(""); + setDb(false); + }, 100); } }, [globalVariablesEntries]); - function handleDelete(key: string) { + useEffect(() => { + if ( + !data.node?.template[name].value && + data.node?.template[name].display_name + ) { + if (unavaliableFields[data.node?.template[name].display_name!] && !disabled) { + setTimeout(() => { + setDb(true); + onChange(unavaliableFields[data.node?.template[name].display_name!]); + }, 100); + } + } + }, [unavaliableFields]); + + async function handleDelete(key: string) { const id = getVariableId(key); if (id !== undefined) { - deleteGlobalVariable(id) - .then((_) => { + await deleteGlobalVariable(id) + .then(() => { removeGlobalVariable(key); if ( data?.node?.template[name].value === key && @@ -54,11 +72,10 @@ export default function InputGlobalComponent({ setDb(false); } }) - .catch((error) => { - let responseError = error as ResponseErrorDetailAPI; + .catch(() => { setErrorData({ title: "Error deleting variable", - list: [responseError.response.data.detail ?? "Unknown error"], + list: [cn("ID not found for variable: ", key)], }); }); } else { @@ -117,7 +134,8 @@ export default function InputGlobalComponent({ )} selectedOption={ - data?.node?.template[name].load_from_db ?? false + data?.node?.template[name].load_from_db && + globalVariablesEntries.includes(data?.node?.template[name].value ?? "") ? data?.node?.template[name].value : "" } diff --git a/src/frontend/src/components/tableComponent/index.tsx b/src/frontend/src/components/tableComponent/index.tsx new file mode 100644 index 000000000..300aa5cc3 --- /dev/null +++ b/src/frontend/src/components/tableComponent/index.tsx @@ -0,0 +1,29 @@ +import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid +import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid +import { AgGridReact } from "ag-grid-react"; +import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react"; +import { useDarkStore } from "../../stores/darkStore"; +import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid +import { cn } from "../../utils/utils"; + +const TableComponent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ pagination = true, ...props }, ref) => { + const dark = useDarkStore((state) => state.dark); + + return ( +
+
+ +
+
+ ); +}); + +export default TableComponent; diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 5569eb309..787dc7664 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -862,13 +862,14 @@ export async function requestLogout() { } export async function getGlobalVariables(): Promise<{ - [key: string]: { id: string; type: string }; + [key: string]: { id: string; type: string; default_fields: string[] }; }> { const globalVariables = {}; (await api.get(`${BASE_URL_API}variables/`)).data.forEach((element) => { globalVariables[element.name] = { id: element.id, type: element.type, + default_fields: element.default_fields, }; }); return globalVariables; @@ -878,20 +879,33 @@ export async function registerGlobalVariable({ name, value, type, + default_fields = [], }: { name: string; value: string; type?: string; + default_fields?: string[]; }): Promise> { - return await api.post(`${BASE_URL_API}variables/`, { - name, - value, - type, - }); + try { + const response = await api.post(`${BASE_URL_API}variables/`, { + name, + value, + type, + default_fields: default_fields, + }); + return response; + } catch (error) { + throw error; + } } export async function deleteGlobalVariable(id: string) { - api.delete(`${BASE_URL_API}variables/${id}`); + try { + const response = await api.delete(`${BASE_URL_API}variables/${id}`); + return response; + } catch (error) { + throw error; + } } export async function updateGlobalVariable( @@ -899,10 +913,16 @@ export async function updateGlobalVariable( value: string, id: string ) { - api.patch(`${BASE_URL_API}variables/${id}`, { - name, - value, - }); + try { + const response = api.patch(`${BASE_URL_API}variables/${id}`, { + name, + value, + }); + + return response; + } catch (error) { + throw error; + } } export async function getVerticesOrder( diff --git a/src/frontend/src/pages/SettingsPage/index.tsx b/src/frontend/src/pages/SettingsPage/index.tsx new file mode 100644 index 000000000..554be79b6 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/index.tsx @@ -0,0 +1,91 @@ +import { useEffect } from "react"; +import { Outlet } from "react-router-dom"; +import ForwardedIconComponent from "../../components/genericIconComponent"; +import PageLayout from "../../components/pageLayout"; +import SidebarNav from "../../components/sidebarComponent"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; + +export default function SettingsPage(): JSX.Element { + const pathname = location.pathname; + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); + useEffect(() => { + setCurrentFlowId(""); + }, [pathname]); + + const sidebarNavItems = [ + { + title: "General", + href: "/settings/general", + icon: ( + + ), + }, + /* { + title: "Theme", + href: "/settings/theme", + icon: ( + + ), + }, + { + title: "Bundles", + href: "/settings/bundles", + icon: ( + + ), + }, + { + title: "Integrations", + href: "/settings/integrations", + icon: ( + + ), + }, */ + { + title: "Global Variables", + href: "/settings/global-variables", + icon: ( + + ), + }, + { + title: "Shortcuts", + href: "/settings/shortcuts", + icon: ( + + ), + }, + ]; + return ( + +
+ +
+ +
+
+
+ ); +} diff --git a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx new file mode 100644 index 000000000..ac2af4f75 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx @@ -0,0 +1,226 @@ +import * as Form from "@radix-ui/react-form"; +import { cloneDeep } from "lodash"; +import { useContext, useEffect, useState } from "react"; +import ForwardedIconComponent from "../../../../components/genericIconComponent"; +import GradientChooserComponent from "../../../../components/gradientChooserComponent"; +import InputComponent from "../../../../components/inputComponent"; +import { Button } from "../../../../components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "../../../../components/ui/card"; +import { + EDIT_PASSWORD_ALERT_LIST, + EDIT_PASSWORD_ERROR_ALERT, + SAVE_ERROR_ALERT, + SAVE_SUCCESS_ALERT, +} from "../../../../constants/alerts_constants"; +import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants"; +import { AuthContext } from "../../../../contexts/authContext"; +import { resetPassword, updateUser } from "../../../../controllers/API"; +import useAlertStore from "../../../../stores/alertStore"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { + inputHandlerEventType, + patchUserInputStateType, +} from "../../../../types/components"; +import { gradients } from "../../../../utils/styleUtils"; + +export default function GeneralPage() { + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); + + const [inputState, setInputState] = useState( + CONTROL_PATCH_USER_STATE + ); + + const { autoLogin } = useContext(AuthContext); + + // set null id + useEffect(() => { + setCurrentFlowId(""); + }, []); + const setSuccessData = useAlertStore((state) => state.setSuccessData); + const setErrorData = useAlertStore((state) => state.setErrorData); + const { userData, setUserData } = useContext(AuthContext); + const { password, cnfPassword, gradient } = inputState; + + async function handlePatchPassword() { + if (password !== cnfPassword) { + setErrorData({ + title: EDIT_PASSWORD_ERROR_ALERT, + list: [EDIT_PASSWORD_ALERT_LIST], + }); + return; + } + try { + if (password !== "") await resetPassword(userData!.id, { password }); + handleInput({ target: { name: "password", value: "" } }); + handleInput({ target: { name: "cnfPassword", value: "" } }); + setSuccessData({ title: SAVE_SUCCESS_ALERT }); + } catch (error) { + setErrorData({ + title: SAVE_ERROR_ALERT, + list: [(error as any).response.data.detail], + }); + } + } + + async function handlePatchGradient() { + try { + if (gradient !== "") + await updateUser(userData!.id, { profile_image: gradient }); + if (gradient !== "") { + let newUserData = cloneDeep(userData); + newUserData!.profile_image = gradient; + + setUserData(newUserData); + } + setSuccessData({ title: SAVE_SUCCESS_ALERT }); + } catch (error) { + setErrorData({ + title: SAVE_ERROR_ALERT, + list: [(error as any).response.data.detail], + }); + } + } + + function handleInput({ + target: { name, value }, + }: inputHandlerEventType): void { + setInputState((prev) => ({ ...prev, [name]: value })); + } + return ( +
+
+
+

+ General + +

+

+ Manage settings related to Langflow and your account. +

+
+
+ +
+ {!autoLogin && ( + <> + { + handlePatchGradient(); + event.preventDefault(); + }} + > + + + Profile Gradient + + Choose the gradient that appears as your profile picture. + + + +
+ { + handleInput({ target: { name: "gradient", value } }); + }} + /> +
+
+ + + + + +
+
+ { + handlePatchPassword(); + event.preventDefault(); + }} + > + + + Password + + Type your new password and confirm it. + + + +
+ + { + handleInput({ target: { name: "password", value } }); + }} + value={password} + isForm + password={true} + placeholder="Password" + className="w-full" + /> + + Please enter your password + + + + { + handleInput({ + target: { name: "cnfPassword", value }, + }); + }} + value={cnfPassword} + isForm + password={true} + placeholder="Confirm Password" + className="w-full" + /> + + + Please confirm your password + + +
+
+ + + + + +
+
+ + )} +
+
+ ); +} diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx new file mode 100644 index 000000000..6bf31f245 --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx @@ -0,0 +1,183 @@ +import IconComponent from "../../../../components/genericIconComponent"; +import { Button } from "../../../../components/ui/button"; + +import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; +import { useEffect, useState } from "react"; +import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton"; +import Dropdown from "../../../../components/dropdownComponent"; +import ForwardedIconComponent from "../../../../components/genericIconComponent"; +import TableComponent from "../../../../components/tableComponent"; +import { Badge } from "../../../../components/ui/badge"; +import { deleteGlobalVariable } from "../../../../controllers/API"; +import useAlertStore from "../../../../stores/alertStore"; +import { useGlobalVariablesStore } from "../../../../stores/globalVariables"; +import { cn } from "../../../../utils/utils"; + +export default function GlobalVariablesPage() { + const globalVariablesEntries = useGlobalVariablesStore( + (state) => state.globalVariablesEntries + ); + const removeGlobalVariable = useGlobalVariablesStore( + (state) => state.removeGlobalVariable + ); + const globalVariables = useGlobalVariablesStore( + (state) => state.globalVariables + ); + const setErrorData = useAlertStore((state) => state.setErrorData); + const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); + + const BadgeRenderer = (props) => { + return props.value !== "" ? ( +
+ + {props.value} + +
+ ) : ( +
+ ); + }; + + const [rowData, setRowData] = useState< + { + type: string | undefined; + id: string; + name: string; + default_fields: string | undefined; + }[] + >(); + + useEffect(() => { + const rows: Array<{ + type: string | undefined; + id: string; + name: string; + default_fields: string | undefined; + }> = []; + globalVariablesEntries.forEach((entrie) => { + const globalVariableObj = globalVariables[entrie]; + rows.push({ + type: globalVariableObj.type, + id: globalVariableObj.id, + default_fields: (globalVariableObj.default_fields ?? []).join(", "), + name: entrie, + }); + }); + setRowData(rows); + }, [globalVariables]); + + const DropdownEditor = ({ options, value, onValueChange }) => { + return ( + +
+
+ ); + }; + // Column Definitions: Defines the columns to be displayed. + const [colDefs, setColDefs] = useState<(ColDef | ColGroupDef)[]>([ + { + headerCheckboxSelection: true, + checkboxSelection: true, + showDisabledCheckboxes: true, + headerName: "Variable Name", + field: "name", + flex: 1, + }, //This column will be twice as wide as the others + { + field: "type", + cellRenderer: BadgeRenderer, + cellEditor: DropdownEditor, + cellEditorParams: { + options: ["Generic", "Credential"], + }, + flex: 1, + editable: false, + }, + // { + // field: "value", + // cellEditor: "agLargeTextCellEditor", + // flex: 2, + // editable: false, + // }, + { + headerName: "Apply To Fields", + field: "default_fields", + flex: 1, + editable: false, + }, + ]); + + const [selectedRows, setSelectedRows] = useState([]); + + async function removeVariables() { + const deleteGlobalVariablesPromise = selectedRows.map(async (row) => { + const id = getVariableId(row); + const deleteGlobalVariables = deleteGlobalVariable(id!); + await deleteGlobalVariables; + }); + Promise.all(deleteGlobalVariablesPromise) + .then(() => { + selectedRows.forEach((row) => { + removeGlobalVariable(row); + }); + }) + .catch(() => { + setErrorData({ + title: `Error deleting global variables.`, + }); + }); + } + + return ( +
+
+
+

+ Global Variables + +

+

+ Manage global variables and assign them to fields. +

+
+
+ + + + +
+
+ +
+ { + setSelectedRows(event.api.getSelectedRows().map((row) => row.name)); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + columnDefs={colDefs} + rowData={rowData} + /> +
+
+ ); +} diff --git a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx new file mode 100644 index 000000000..de2c92b2d --- /dev/null +++ b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx @@ -0,0 +1,119 @@ +import { ColDef, ColGroupDef } from "ag-grid-community"; +import { useState } from "react"; +import ForwardedIconComponent from "../../../../components/genericIconComponent"; +import TableComponent from "../../../../components/tableComponent"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../../../../components/ui/card"; + +export default function ShortcutsPage() { + const advancedShortcut = "Ctrl + Shift + A"; + const minizmizeShortcut = "Ctrl + Shift + Q"; + const codeShortcut = "Ctrl + Shift + C"; + const copyShortcut = "Ctrl + C"; + const duplicateShortcut = "Ctrl + D"; + const shareShortcut = "Ctrl + Shift + S"; + const docsShortcut = "Ctrl + Shift + D"; + const saveShortcut = "Ctrl + S"; + const deleteShortcut = "Backspace"; + const interactionShortcut = "Ctrl + K"; + const undoShortcut = "Ctrl + Z"; + const redoShortcut = "Ctrl + Y"; + + // Column Definitions: Defines the columns to be displayed. + const [colDefs, setColDefs] = useState<(ColDef | ColGroupDef)[]>([ + { headerName: "Functionality", field: "name", flex: 1, editable: false }, //This column will be twice as wide as the others + { + field: "shortcut", + flex: 2, + editable: false, + }, + ]); + + const [nodesRowData, setNodesRowData] = useState([ + { + name: "Advanced Settings Component", + shortcut: advancedShortcut, + }, + { + name: "Minimize Component", + shortcut: minizmizeShortcut, + }, + { + name: "Code Component", + shortcut: codeShortcut, + }, + { + name: "Copy Component", + shortcut: copyShortcut, + }, + { + name: "Duplicate Component", + shortcut: duplicateShortcut, + }, + { + name: "Share Component", + shortcut: shareShortcut, + }, + { + name: "Docs Component", + shortcut: docsShortcut, + }, + { + name: "Save Component", + shortcut: saveShortcut, + }, + { + name: "Delete Component", + shortcut: deleteShortcut, + }, + { + name: "Open Playground", + shortcut: interactionShortcut, + }, + { + name: "Undo", + shortcut: undoShortcut, + }, + { + name: "Redo", + shortcut: redoShortcut, + }, + ]); + + return ( +
+
+
+

+ Shortcuts + +

+

+ Manage Shortcuts for quick access to + frequently used actions. +

+
+
+
+ + + + + +
+
+ ); +} diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 036c19fa1..cc47e1f28 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -10,7 +10,10 @@ import ApiKeysPage from "./pages/ApiKeysPage"; import FlowPage from "./pages/FlowPage"; import HomePage from "./pages/MainPage"; import ComponentsComponent from "./pages/MainPage/components/components"; -import ProfileSettingsPage from "./pages/ProfileSettingsPage"; +import SettingsPage from "./pages/SettingsPage"; +import GeneralPage from "./pages/SettingsPage/pages/GeneralPage"; +import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage"; +import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage"; import StorePage from "./pages/StorePage"; import ViewPage from "./pages/ViewPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; @@ -39,6 +42,19 @@ const Router = () => { element={} /> + + + + } + > + } /> + } /> + } /> + } /> + { /> - - - - } - /> ((set, get) => ({ ); useTypesStore.setState((state) => ({ data: { ...state.data, ["saved_components"]: data }, + ComponentFields: extractFieldsFromComponenents({ + ...state.data, + ["saved_components"]: data, + }), })); set({ isLoading: false }); resolve(); @@ -207,6 +212,10 @@ const useFlowsManagerStore = create((set, get) => ({ set({ isLoading: false }); useTypesStore.setState((state) => ({ data: { ...state.data, ["saved_components"]: data }, + ComponentFields: extractFieldsFromComponenents({ + ...state.data, + ["saved_components"]: data, + }), })); }, 200); // addFlowToLocalState(newFlow); @@ -229,6 +238,10 @@ const useFlowsManagerStore = create((set, get) => ({ set({ isLoading: false }); useTypesStore.setState((state) => ({ data: { ...state.data, ["saved_components"]: data }, + ComponentFields: extractFieldsFromComponenents({ + ...state.data, + ["saved_components"]: data, + }), })); // Return the id @@ -258,6 +271,10 @@ const useFlowsManagerStore = create((set, get) => ({ set({ isLoading: false }); useTypesStore.setState((state) => ({ data: { ...state.data, ["saved_components"]: data }, + ComponentFields: extractFieldsFromComponenents({ + ...state.data, + ["saved_components"]: data, + }), })); resolve(); }); diff --git a/src/frontend/src/stores/globalVariables.ts b/src/frontend/src/stores/globalVariables.ts index e2f3e37df..3adf8cbf8 100644 --- a/src/frontend/src/stores/globalVariables.ts +++ b/src/frontend/src/stores/globalVariables.ts @@ -1,30 +1,45 @@ import { create } from "zustand"; import { GlobalVariablesStore } from "../types/zustand/globalVariables"; +import { getUnavailableFields } from "../utils/utils"; export const useGlobalVariablesStore = create( (set, get) => ({ + unavaliableFields: {}, + setUnavaliableFields: (fields) => { + set({ unavaliableFields: fields }); + }, + removeUnavaliableField: (field) => { + const newFields = get().unavaliableFields; + delete newFields[field]; + set({ unavaliableFields: newFields }); + }, globalVariablesEntries: [], globalVariables: {}, setGlobalVariables: (variables) => { set({ globalVariables: variables, globalVariablesEntries: Object.keys(variables), + unavaliableFields: getUnavailableFields(variables), }); }, - addGlobalVariable: (name, id, type) => { - const data = { id, type }; + addGlobalVariable: (name, id, type, default_fields) => { + const data = { id, type, default_fields }; const newVariables = { ...get().globalVariables, [name]: data }; set({ globalVariables: newVariables, globalVariablesEntries: Object.keys(newVariables), + unavaliableFields: getUnavailableFields(newVariables), }); }, - removeGlobalVariable: (name) => { + removeGlobalVariable: async (name) => { + const id = get().globalVariables[name]?.id; + if (id === undefined) return; const newVariables = { ...get().globalVariables }; delete newVariables[name]; set({ globalVariables: newVariables, globalVariablesEntries: Object.keys(newVariables), + unavaliableFields: getUnavailableFields(newVariables), }); }, getVariableId: (name) => { diff --git a/src/frontend/src/stores/typesStore.ts b/src/frontend/src/stores/typesStore.ts index 142118f44..c92236e03 100644 --- a/src/frontend/src/stores/typesStore.ts +++ b/src/frontend/src/stores/typesStore.ts @@ -2,11 +2,22 @@ import { create } from "zustand"; import { getAll } from "../controllers/API"; import { APIDataType } from "../types/api"; import { TypesStoreType } from "../types/zustand/types"; -import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils"; +import { + extractFieldsFromComponenents, + templatesGenerator, + typesGenerator, +} from "../utils/reactflowUtils"; import useAlertStore from "./alertStore"; import useFlowsManagerStore from "./flowsManagerStore"; export const useTypesStore = create((set, get) => ({ + ComponentFields: new Set(), + setComponentFields: (fields) => { + set({ ComponentFields: fields }); + }, + addComponentField: (field) => { + set({ ComponentFields: get().ComponentFields.add(field) }); + }, types: {}, templates: {}, data: {}, @@ -21,6 +32,10 @@ export const useTypesStore = create((set, get) => ({ set((old) => ({ types: typesGenerator(data), data: { ...old.data, ...data }, + ComponentFields: extractFieldsFromComponenents({ + ...old.data, + ...data, + }), templates: templatesGenerator(data), })); setLoading(false); @@ -43,5 +58,6 @@ export const useTypesStore = create((set, get) => ({ setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => { let newChange = typeof change === "function" ? change(get().data) : change; set({ data: newChange }); + get().setComponentFields(extractFieldsFromComponenents(newChange)); }, })); diff --git a/src/frontend/src/style/ag-theme-shadcn.css b/src/frontend/src/style/ag-theme-shadcn.css new file mode 100644 index 000000000..3605d53b3 --- /dev/null +++ b/src/frontend/src/style/ag-theme-shadcn.css @@ -0,0 +1,21 @@ +/* set the background color of many elements across the grid */ +.ag-theme-shadcn { + --ag-foreground-color: hsl(var(--foreground)); + --ag-background-color: hsl(var(--background)); + --ag-secondary-foreground-color: hsl(var(--secondary-foreground)); + --ag-data-color: hsl(var(--foreground)); + --ag-header-foreground-color: hsl(var(--muted-foreground)); + --ag-header-background-color: hsl(var(--background)); + --ag-tooltip-background-color: hsl(var(--muted)); + --ag-disabled-foreground-color: hsl(var(--muted-foreground)); + --ag-border-color: hsl(var(--border)); + --ag-selected-row-background-color: hsl(var(--accent)); + --ag-menu-background-color: hsl(var(--accent)); + --ag-panel-background-color: hsl(var(--accent)); + --ag-row-hover-color: hsl(var(--accent)); + --ag-header-height: 2.5rem; +} + +.ag-theme-shadcn .ag-paging-panel { + height: 3rem; +} diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 9c085be48..2a3ee29b7 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -28,6 +28,8 @@ export type InputComponentType = { optionButton?: (option: string) => ReactElement; selectedOption?: string; setSelectedOption?: (value: string) => void; + selectedOptions?: string[]; + setSelectedOptions?: (value: string[]) => void; }; export type ToggleComponentType = { enabled: boolean; @@ -45,6 +47,7 @@ export type DropDownComponentType = { onSelect: (value: string) => void; editNode?: boolean; id?: string; + children?: ReactNode; }; export type ParameterComponentType = { data: NodeDataType; @@ -72,15 +75,6 @@ export type InputListComponentType = { playgroundDisabled?: boolean; }; -export type InputGlobalComponentType = { - disabled: boolean; - onChange: (value: string) => void; - setDb: (value: boolean) => void; - name: string; - data: NodeDataType; - editNode?: boolean; -}; - export type KeyPairListComponentType = { value: any; onChange: (value: Object[]) => void; diff --git a/src/frontend/src/types/zustand/globalVariables/index.ts b/src/frontend/src/types/zustand/globalVariables/index.ts index 3e651179e..752336c19 100644 --- a/src/frontend/src/types/zustand/globalVariables/index.ts +++ b/src/frontend/src/types/zustand/globalVariables/index.ts @@ -1,10 +1,31 @@ export type GlobalVariablesStore = { globalVariablesEntries: Array; - globalVariables: { [name: string]: { id: string; type?: string } }; + globalVariables: { + [name: string]: { + id: string; + type?: string; + default_fields?: string[]; + value?: string; + }; + }; setGlobalVariables: (variables: { - [name: string]: { id: string; type?: string }; + [name: string]: { + id: string; + type?: string; + default_fields?: string[]; + value?: string; + }; }) => void; - addGlobalVariable: (name: string, id: string, type?: string) => void; - removeGlobalVariable: (name: string) => void; + addGlobalVariable: ( + name: string, + id: string, + type?: string, + default_fields?: string[], + value?: string + ) => void; + removeGlobalVariable: (name: string) => Promise; getVariableId: (name: string) => string | undefined; + unavaliableFields: {[name: string]: string}; + setUnavaliableFields: (fields: {[name: string]: string}) => void; + removeUnavaliableField: (field: string) => void; }; diff --git a/src/frontend/src/types/zustand/types/index.ts b/src/frontend/src/types/zustand/types/index.ts index 133afbda1..4f817de47 100644 --- a/src/frontend/src/types/zustand/types/index.ts +++ b/src/frontend/src/types/zustand/types/index.ts @@ -8,4 +8,7 @@ export type TypesStoreType = { data: APIDataType; setData: (newState: {}) => void; getTypes: () => Promise; + ComponentFields: Set; + setComponentFields: (fields: Set) => void; + addComponentField: (field: string) => void; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 4cd19bf7e..deb470f80 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -1234,6 +1234,22 @@ export function templatesGenerator(data: APIObjectType) { }, {}); } +export function extractFieldsFromComponenents(data: APIObjectType) { + const fields = new Set(); + Object.keys(data).forEach((key) => { + Object.keys(data[key]).forEach((kind) => { + Object.keys(data[key][kind].template).forEach((field) => { + if ( + data[key][kind].template[field].display_name && + data[key][kind].template[field].show + ) + fields.add(data[key][kind].template[field].display_name!); + }); + }); + }); + return fields; +} + export function downloadFlow( flow: FlowType, flowName: string, diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 32e480046..70bea00d4 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -5,6 +5,7 @@ import { ArrowUpToLine, Bell, Binary, + Blocks, BookMarked, BookmarkPlus, Bot, @@ -67,6 +68,7 @@ import { Home, Info, Key, + Keyboard, Laptop2, Layers, Link, @@ -86,6 +88,7 @@ import { MoreHorizontal, Network, Package2, + Palette, Paperclip, Pencil, PencilLine, @@ -108,6 +111,7 @@ import { Share2, Shield, Sliders, + SlidersHorizontal, Snowflake, Sparkles, Square, @@ -376,6 +380,7 @@ export const nodeIconsLucide: iconsType = { tools: Hammer, advanced: Laptop2, chat: MessageCircle, + Keyboard: Keyboard, embeddings: Binary, saved_components: GradientSave, documentloaders: Paperclip, @@ -414,6 +419,9 @@ export const nodeIconsLucide: iconsType = { MoonIcon, Bell, ChevronLeft, + SlidersHorizontal, + Palette, + Blocks, ChevronDown, ArrowLeft, Shield, diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index afcc264f6..1c580fa4b 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -91,6 +91,20 @@ export function toTitleCase( .join(" "); } +export function getUnavailableFields(variables: { + [key: string]: { default_fields?: string[] }; +}): {[name: string]: string} { + const unVariables:{[name: string]: string} = {}; + Object.keys(variables).forEach((key) => { + if (variables[key].default_fields) { + variables[key].default_fields!.forEach((field) => { + unVariables[field] = key; + }); + } + }); + return unVariables; +} + export const upperCaseWords: string[] = ["llm", "uri"]; export function checkUpperWords(str: string): string { const words = str.split(" ").map((word) => {