Feature/UI table (#1825)

This commit is contained in:
Cristhian Zanforlin Lousa 2024-05-02 14:57:39 -03:00 committed by GitHub
commit bdca2e3a40
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 1162 additions and 154 deletions

View file

@ -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 ###

View file

@ -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 ###

View file

@ -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

View file

@ -1,6 +1,5 @@
from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_openai import AzureChatOpenAI
from pydantic.v1 import SecretStr

View file

@ -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):

View file

@ -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):

View file

@ -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")

View file

@ -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)

View file

@ -0,0 +1 @@
build/*

View file

@ -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",

View file

@ -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": {

View file

@ -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);

View file

@ -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<string[]>([]);
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"
/>
<Label>Apply To Fields (optional)</Label>
<InputComponent
setSelectedOptions={(value) => setFields(value)}
selectedOptions={fields}
password={false}
options={availableFields}
placeholder="Choose a field for the variable..."
></InputComponent>
</div>
</BaseModal.Content>
<BaseModal.Footer>

View file

@ -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<HTMLButtonElement>(null);
const PopoverContentDropdown = children
? PopoverContent
: PopoverContentWithoutPortal;
return (
<>
{Object.keys(options)?.length > 0 ? (
<>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
disabled={disabled}
variant="primary"
size="xs"
role="combobox"
ref={refButton}
aria-expanded={open}
data-testid={`${id ?? ""}`}
className={cn(
editNode
? "dropdown-component-outline"
: "dropdown-component-false-outline",
"w-full justify-between font-normal",
editNode ? "input-edit-node" : "py-2"
)}
>
<span data-testid={`value-dropdown-` + id}>
{value &&
value !== "" &&
options.find((option) => option === value)
? options.find((option) => option === value)
: "Choose an option..."}
</span>
<Popover open={open} onOpenChange={children ? () => {} : setOpen}>
{children ? (
<PopoverAnchor>{children}</PopoverAnchor>
) : (
<PopoverTrigger asChild>
<Button
disabled={disabled}
variant="primary"
size="xs"
role="combobox"
ref={refButton}
aria-expanded={open}
data-testid={`${id ?? ""}`}
className={cn(
editNode
? "dropdown-component-outline"
: "dropdown-component-false-outline",
"w-full justify-between font-normal",
editNode ? "input-edit-node" : "py-2"
)}
>
<span data-testid={`value-dropdown-` + id}>
{value &&
value !== "" &&
options.find((option) => option === value)
? options.find((option) => option === value)
: "Choose an option..."}
</span>
<ForwardedIconComponent
name="ChevronsUpDown"
className="ml-2 h-4 w-4 shrink-0 opacity-50"
/>
</Button>
</PopoverTrigger>
<PopoverContentWithoutPortal
className="nocopy nowheel nopan nodelete nodrag noundo w-full p-0"
style={{ minWidth: refButton?.current?.clientWidth ?? "200px" }}
<ForwardedIconComponent
name="ChevronsUpDown"
className="ml-2 h-4 w-4 shrink-0 opacity-50"
/>
</Button>
</PopoverTrigger>
)}
<PopoverContentDropdown
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={
children
? {}
: { minWidth: refButton?.current?.clientWidth ?? "200px" }
}
>
<Command>
<CommandInput placeholder="Search options..." className="h-9" />
@ -98,7 +113,7 @@ export default function Dropdown({
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</PopoverContentDropdown>
</Popover>
</>
) : (

View file

@ -2,7 +2,7 @@ import { gradients } from "../../utils/styleUtils";
export default function GradientChooserComponent({ value, onChange }) {
return (
<div className="flex flex-wrap items-center justify-center gap-4">
<div className="flex flex-wrap items-center justify-start gap-2">
{gradients.map((gradient, idx) => (
<div
onClick={() => {

View file

@ -168,50 +168,56 @@ export default function Header(): JSX.Element {
/>
</button>
)}
{!autoLogin && (
<>
<Separator orientation="vertical" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
])
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
{isAdmin && (
<>
<Separator orientation="vertical" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: "bg-gray-500"))
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>General</DropdownMenuLabel>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/settings")}
>
Settings
</DropdownMenuItem>
{!autoLogin && (
<>
<DropdownMenuSeparator />
<DropdownMenuLabel>My Account</DropdownMenuLabel>
{isAdmin && (
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/admin")}
>
Admin Page
</DropdownMenuItem>
)}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/admin")}
onClick={() => {
logout();
}}
>
Admin Page
Sign Out
</DropdownMenuItem>
)}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/account/settings")}
>
Profile Settings
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
logout();
}}
>
Sign Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
</div>
</div>
</div>

View file

@ -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({
>
<Command
filter={(value, search) => {
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);
}}
>
<div className="group flex w-full items-center justify-between">
@ -192,7 +212,8 @@ export default function InputComponent({
<div
className={cn(
"relative mr-2 h-4 w-4",
selectedOption === option
selectedOption === option ||
selectedOptions?.includes(option)
? "opacity-100"
: "opacity-0"
)}
@ -228,12 +249,16 @@ export default function InputComponent({
<div
className={cn(
"pointer-events-auto absolute inset-y-0 h-full w-full cursor-pointer",
(selectedOption !== "" || !onChange) && setSelectedOption
((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)
? ""
: "hidden"
)}
onClick={
(selectedOption !== "" || !onChange) && setSelectedOption
((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)
? (e) => {
setShowOptions((old) => !old);
e.preventDefault();
@ -245,7 +270,7 @@ export default function InputComponent({
</>
)}
{setSelectedOption && (
{(setSelectedOption || setSelectedOptions) && (
<span
className={cn(
password && selectedOption === "" ? "right-8" : "right-0",

View file

@ -3,7 +3,6 @@ import { deleteGlobalVariable } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { ResponseErrorDetailAPI } from "../../types/api";
import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton";
@ -24,6 +23,9 @@ export default function InputGlobalComponent({
);
const getVariableId = useGlobalVariablesStore((state) => 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({
</DeleteConfirmationModal>
)}
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
: ""
}

View file

@ -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<typeof AgGridReact>,
ComponentPropsWithoutRef<typeof AgGridReact>
>(({ pagination = true, ...props }, ref) => {
const dark = useDarkStore((state) => state.dark);
return (
<div className="flex h-full flex-col">
<div
className={cn(
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col"
)} // applying the grid theme
>
<AgGridReact ref={ref} {...props} />
</div>
</div>
);
});
export default TableComponent;

View file

@ -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<AxiosResponse<{ name: string; id: string; type: string }>> {
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(

View file

@ -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: (
<ForwardedIconComponent
name="SlidersHorizontal"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
/>
),
},
/* {
title: "Theme",
href: "/settings/theme",
icon: (
<ForwardedIconComponent
name="Palette"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
/>
),
},
{
title: "Bundles",
href: "/settings/bundles",
icon: (
<ForwardedIconComponent
name="Boxes"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
/>
),
},
{
title: "Integrations",
href: "/settings/integrations",
icon: (
<ForwardedIconComponent
name="Blocks"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
/>
),
}, */
{
title: "Global Variables",
href: "/settings/global-variables",
icon: (
<ForwardedIconComponent
name="Globe"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
/>
),
},
{
title: "Shortcuts",
href: "/settings/shortcuts",
icon: (
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" />
),
},
];
return (
<PageLayout
title="Settings"
description="Manage the general settings for Langflow."
>
<div className="flex h-full w-full space-y-8 lg:flex-row lg:space-x-8 lg:space-y-0">
<aside className="flex h-full flex-col space-y-6 lg:w-1/5">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="h-full w-full flex-1">
<Outlet />
</div>
</div>
</PageLayout>
);
}

View file

@ -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<patchUserInputStateType>(
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 (
<div className="flex h-full w-full flex-col gap-6">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
General
<ForwardedIconComponent
name="SlidersHorizontal"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage settings related to Langflow and your account.
</p>
</div>
</div>
<div className="grid gap-6">
{!autoLogin && (
<>
<Form.Root
onSubmit={(event) => {
handlePatchGradient();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: gradient
}
onChange={(value) => {
handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
<Form.Root
onSubmit={(event) => {
handlePatchPassword();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Type your new password and confirm it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full gap-4">
<Form.Field name="password" className="w-full">
<InputComponent
id="pasword"
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message
match="valueMissing"
className="field-invalid"
>
Please enter your password
</Form.Message>
</Form.Field>
<Form.Field name="cnfPassword" className="w-full">
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({
target: { name: "cnfPassword", value },
});
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/>
<Form.Message
className="field-invalid"
match="valueMissing"
>
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
</>
)}
</div>
</div>
);
}

View file

@ -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 !== "" ? (
<div>
<Badge variant="outline" size="md" className="font-normal">
{props.value}
</Badge>
</div>
) : (
<div></div>
);
};
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 (
<Dropdown options={options} value={value} onSelect={onValueChange}>
<div className="-mt-1.5 w-full"></div>
</Dropdown>
);
};
// Column Definitions: Defines the columns to be displayed.
const [colDefs, setColDefs] = useState<(ColDef<any> | ColGroupDef<any>)[]>([
{
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<string[]>([]);
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 (
<div className="flex h-full w-full flex-col justify-between gap-6">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
Global Variables
<ForwardedIconComponent
name="Globe"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage global variables and assign them to fields.
</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<Button
data-testid="api-key-button-store"
variant="primary"
className="group px-2"
disabled={selectedRows.length === 0}
onClick={removeVariables}
>
<IconComponent
name="Trash2"
className={cn(
"h-5 w-5 text-destructive group-disabled:text-primary"
)}
/>
</Button>
<AddNewVariableButton>
<Button data-testid="api-key-button-store" variant="primary">
<IconComponent name="Plus" className="mr-2 w-4" />
Add New
</Button>
</AddNewVariableButton>
</div>
</div>
<div className="flex h-full w-full flex-col justify-between pb-8">
<TableComponent
onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
}}
rowSelection="multiple"
suppressRowClickSelection={true}
columnDefs={colDefs}
rowData={rowData}
/>
</div>
</div>
);
}

View file

@ -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<any> | ColGroupDef<any>)[]>([
{ 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 (
<div className="flex h-full w-full flex-col gap-6">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
Shortcuts
<ForwardedIconComponent
name="Keyboard"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage Shortcuts for quick access to
frequently used actions.
</p>
</div>
</div>
<div className="grid gap-6 pb-8">
<Card x-chunk="dashboard-04-chunk-2" className="pt-4">
<CardContent>
<TableComponent
domLayout="autoHeight"
pagination={false}
columnDefs={colDefs}
rowData={nodesRowData}
/>
</CardContent>
</Card>
</div>
</div>
);
}

View file

@ -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={<ComponentsComponent key="components" />}
/>
</Route>
<Route
path="/settings"
element={
<ProtectedRoute>
<SettingsPage />
</ProtectedRoute>
}
>
<Route index element={<Navigate replace to={"general"} />} />
<Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="general" element={<GeneralPage />} />
<Route path="shortcuts" element={<ShortcutsPage />} />
</Route>
<Route
path="/store"
element={
@ -128,14 +144,6 @@ const Router = () => {
/>
<Route path="/account">
<Route
path="settings"
element={
<ProtectedRoute>
<ProfileSettingsPage />
</ProtectedRoute>
}
/>
<Route
path="delete"
element={

View file

@ -22,6 +22,7 @@ import {
addVersionToDuplicates,
createFlowComponent,
createNewFlow,
extractFieldsFromComponenents,
processDataFromFlow,
processFlows,
} from "../utils/reactflowUtils";
@ -92,6 +93,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((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<FlowsManagerStoreType>((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<FlowsManagerStoreType>((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<FlowsManagerStoreType>((set, get) => ({
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
ComponentFields: extractFieldsFromComponenents({
...state.data,
["saved_components"]: data,
}),
}));
resolve();
});

View file

@ -1,30 +1,45 @@
import { create } from "zustand";
import { GlobalVariablesStore } from "../types/zustand/globalVariables";
import { getUnavailableFields } from "../utils/utils";
export const useGlobalVariablesStore = create<GlobalVariablesStore>(
(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) => {

View file

@ -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<TypesStoreType>((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<TypesStoreType>((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<TypesStoreType>((set, get) => ({
setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => {
let newChange = typeof change === "function" ? change(get().data) : change;
set({ data: newChange });
get().setComponentFields(extractFieldsFromComponenents(newChange));
},
}));

View file

@ -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;
}

View file

@ -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;

View file

@ -1,10 +1,31 @@
export type GlobalVariablesStore = {
globalVariablesEntries: Array<string>;
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<void>;
getVariableId: (name: string) => string | undefined;
unavaliableFields: {[name: string]: string};
setUnavaliableFields: (fields: {[name: string]: string}) => void;
removeUnavaliableField: (field: string) => void;
};

View file

@ -8,4 +8,7 @@ export type TypesStoreType = {
data: APIDataType;
setData: (newState: {}) => void;
getTypes: () => Promise<void>;
ComponentFields: Set<string>;
setComponentFields: (fields: Set<string>) => void;
addComponentField: (field: string) => void;
};

View file

@ -1234,6 +1234,22 @@ export function templatesGenerator(data: APIObjectType) {
}, {});
}
export function extractFieldsFromComponenents(data: APIObjectType) {
const fields = new Set<string>();
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,

View file

@ -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,

View file

@ -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) => {