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 uuid import UUID
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
@ -37,7 +37,11 @@ def create_variable(
variable_dict["user_id"] = current_user.id variable_dict["user_id"] = current_user.id
db_variable = Variable.model_validate(variable_dict) 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") raise HTTPException(status_code=400, detail="Variable value cannot be empty")
encrypted = auth_utils.encrypt_api_key(db_variable.value, settings_service=settings_service) encrypted = auth_utils.encrypt_api_key(db_variable.value, settings_service=settings_service)
db_variable.value = encrypted db_variable.value = encrypted
@ -85,7 +89,7 @@ def update_variable(
variable_data = variable.model_dump(exclude_unset=True) variable_data = variable.model_dump(exclude_unset=True)
for key, value in variable_data.items(): for key, value in variable_data.items():
setattr(db_variable, key, value) setattr(db_variable, key, value)
db_variable.updated_at = datetime.utcnow() db_variable.updated_at = datetime.now(timezone.utc)
session.commit() session.commit()
session.refresh(db_variable) session.refresh(db_variable)
return db_variable return db_variable

View file

@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_openai import AzureChatOpenAI from langchain_openai import AzureChatOpenAI
from pydantic.v1 import SecretStr 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.constants import STREAM_INFO_TEXT
from langflow.base.models.model import LCModelComponent from langflow.base.models.model import LCModelComponent
from langflow.field_typing import BaseLanguageModel, Text from langflow.field_typing import Text
class ChatLiteLLMModelComponent(LCModelComponent): class ChatLiteLLMModelComponent(LCModelComponent):

View file

@ -4,6 +4,7 @@ from typing import Literal, Optional, cast
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from pydantic import BaseModel, model_validator from pydantic import BaseModel, model_validator
from langchain_core.messages import HumanMessage, AIMessage
class Record(BaseModel): class Record(BaseModel):

View file

@ -1,8 +1,8 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4 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: if TYPE_CHECKING:
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
@ -13,8 +13,9 @@ def utc_now():
class VariableBase(SQLModel): class VariableBase(SQLModel):
name: Optional[str] = Field(None, description="Name of the variable") name: str = Field(description="Name of the variable")
value: Optional[str] = Field(None, description="Encrypted value 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") 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), sa_column=Column(DateTime(timezone=True), nullable=True),
description="Last update time of the variable", description="Last update time of the variable",
) )
default_fields: Optional[List[str]] = Field(sa_column=Column(JSON))
# foreign key to user table # foreign key to user table
user_id: UUID = Field(description="User ID associated with this variable", foreign_key="user.id") user_id: UUID = Field(description="User ID associated with this variable", foreign_key="user.id")
user: "User" = Relationship(back_populates="variables") user: "User" = Relationship(back_populates="variables")
@ -50,9 +52,11 @@ class VariableRead(SQLModel):
id: UUID id: UUID
name: Optional[str] = Field(None, description="Name of the variable") name: Optional[str] = Field(None, description="Name of the variable")
type: Optional[str] = Field(None, description="Type 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): class VariableUpdate(SQLModel):
id: UUID # Include the ID for updating id: UUID # Include the ID for updating
name: Optional[str] = Field(None, description="Name of the variable") name: Optional[str] = Field(None, description="Name of the variable")
value: Optional[str] = Field(None, description="Encrypted value 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: if var in os.environ:
logger.debug(f"Creating {var} variable from environment.") logger.debug(f"Creating {var} variable from environment.")
try: 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: except Exception as e:
logger.error(f"Error creating {var} variable: {e}") logger.error(f"Error creating {var} variable: {e}")
@ -91,6 +98,7 @@ class VariableService(Service):
user_id: Union[UUID, str], user_id: Union[UUID, str],
name: str, name: str,
value: str, value: str,
default_fields: list[str] = [],
_type: str = "Generic", _type: str = "Generic",
session: Session = Depends(get_session), session: Session = Depends(get_session),
): ):
@ -98,6 +106,7 @@ class VariableService(Service):
name=name, name=name,
type=_type, type=_type,
value=auth_utils.encrypt_api_key(value, settings_service=self.settings_service), 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}) variable = Variable.model_validate(variable_base, from_attributes=True, update={"user_id": user_id})
session.add(variable) session.add(variable)

View file

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

View file

@ -31,6 +31,7 @@
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"ace-builds": "^1.24.1", "ace-builds": "^1.24.1",
"ag-grid-community": "^31.2.1",
"ag-grid-react": "^31.2.1", "ag-grid-react": "^31.2.1",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"axios": "^1.5.0", "axios": "^1.5.0",
@ -49,10 +50,10 @@
"moment": "^2.29.4", "moment": "^2.29.4",
"openseadragon": "^4.1.1", "openseadragon": "^4.1.1",
"playwright": "^1.42.0", "playwright": "^1.42.0",
"react": "^18.2.0", "react": "^18.2.21",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.21",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",

View file

@ -26,6 +26,7 @@
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"ace-builds": "^1.24.1", "ace-builds": "^1.24.1",
"ag-grid-community": "^31.2.1",
"ag-grid-react": "^31.2.1", "ag-grid-react": "^31.2.1",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"axios": "^1.5.0", "axios": "^1.5.0",
@ -44,10 +45,10 @@
"moment": "^2.29.4", "moment": "^2.29.4",
"openseadragon": "^4.1.1", "openseadragon": "^4.1.1",
"playwright": "^1.42.0", "playwright": "^1.42.0",
"react": "^18.2.0", "react": "^18.2.21",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.21",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",
@ -74,7 +75,7 @@
"start": "vite", "start": "vite",
"build": "vite build", "build": "vite build",
"serve": "vite preview", "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" "type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
}, },
"eslintConfig": { "eslintConfig": {

View file

@ -22,7 +22,6 @@ import useFlowsManagerStore from "./stores/flowsManagerStore";
import { useGlobalVariablesStore } from "./stores/globalVariables"; import { useGlobalVariablesStore } from "./stores/globalVariables";
import { useStoreStore } from "./stores/storeStore"; import { useStoreStore } from "./stores/storeStore";
import { useTypesStore } from "./stores/typesStore"; import { useTypesStore } from "./stores/typesStore";
export default function App() { export default function App() {
const removeFromTempNotificationList = useAlertStore( const removeFromTempNotificationList = useAlertStore(
(state) => state.removeFromTempNotificationList (state) => state.removeFromTempNotificationList
@ -48,6 +47,9 @@ export default function App() {
const setGlobalVariables = useGlobalVariablesStore( const setGlobalVariables = useGlobalVariablesStore(
(state) => state.setGlobalVariables (state) => state.setGlobalVariables
); );
const setUnavailableFields = useGlobalVariablesStore(
(state) => state.setUnavaliableFields
);
const checkHasStore = useStoreStore((state) => state.checkHasStore); const checkHasStore = useStoreStore((state) => state.checkHasStore);
const navigate = useNavigate(); const navigate = useNavigate();
const dark = useDarkStore((state) => state.dark); const dark = useDarkStore((state) => state.dark);

View file

@ -3,6 +3,7 @@ import { registerGlobalVariable } from "../../controllers/API";
import BaseModal from "../../modals/baseModal"; import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables"; import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { useTypesStore } from "../../stores/typesStore";
import { ResponseErrorDetailAPI } from "../../types/api"; import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent"; import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent"; import InputComponent from "../inputComponent";
@ -16,25 +17,42 @@ import { Textarea } from "../ui/textarea";
export default function AddNewVariableButton({ children }): JSX.Element { export default function AddNewVariableButton({ children }): JSX.Element {
const [key, setKey] = useState(""); const [key, setKey] = useState("");
const [value, setValue] = 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 [open, setOpen] = useState(false);
const setErrorData = useAlertStore((state) => state.setErrorData); 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( const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable (state) => state.addGlobalVariable
); );
function handleSaveVariable() { function handleSaveVariable() {
let data: { name: string; value: string; type?: string } = { let data: {
name: string;
value: string;
type?: string;
default_fields?: string[];
} = {
name: key, name: key,
type, type,
value, value,
default_fields: fields,
}; };
registerGlobalVariable(data) registerGlobalVariable(data)
.then((res) => { .then((res) => {
const { name, id, type } = res.data; const { name, id, type } = res.data;
addGlobalVariable(name, id, type); addGlobalVariable(name, id, type, fields);
setKey(""); setKey("");
setValue(""); setValue("");
setType(""); setType("");
setFields([]);
setOpen(false); setOpen(false);
}) })
.catch((error) => { .catch((error) => {
@ -89,6 +107,14 @@ export default function AddNewVariableButton({ children }): JSX.Element {
placeholder="Insert a value for the variable..." placeholder="Insert a value for the variable..."
className="w-full resize-none custom-scroll" 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> </div>
</BaseModal.Content> </BaseModal.Content>
<BaseModal.Footer> <BaseModal.Footer>

View file

@ -1,3 +1,4 @@
import { PopoverAnchor } from "@radix-ui/react-popover";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { DropDownComponentType } from "../../types/components"; import { DropDownComponentType } from "../../types/components";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
@ -13,6 +14,7 @@ import {
} from "../ui/command"; } from "../ui/command";
import { import {
Popover, Popover,
PopoverContent,
PopoverContentWithoutPortal, PopoverContentWithoutPortal,
PopoverTrigger, PopoverTrigger,
} from "../ui/popover"; } from "../ui/popover";
@ -25,16 +27,24 @@ export default function Dropdown({
onSelect, onSelect,
editNode = false, editNode = false,
id = "", id = "",
children,
}: DropDownComponentType): JSX.Element { }: DropDownComponentType): JSX.Element {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(children ? true : false);
const refButton = useRef<HTMLButtonElement>(null); const refButton = useRef<HTMLButtonElement>(null);
const PopoverContentDropdown = children
? PopoverContent
: PopoverContentWithoutPortal;
return ( return (
<> <>
{Object.keys(options)?.length > 0 ? ( {Object.keys(options)?.length > 0 ? (
<> <>
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={children ? () => {} : setOpen}>
{children ? (
<PopoverAnchor>{children}</PopoverAnchor>
) : (
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
disabled={disabled} disabled={disabled}
@ -66,9 +76,14 @@ export default function Dropdown({
/> />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContentWithoutPortal )}
className="nocopy nowheel nopan nodelete nodrag noundo w-full p-0" <PopoverContentDropdown
style={{ minWidth: refButton?.current?.clientWidth ?? "200px" }} className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={
children
? {}
: { minWidth: refButton?.current?.clientWidth ?? "200px" }
}
> >
<Command> <Command>
<CommandInput placeholder="Search options..." className="h-9" /> <CommandInput placeholder="Search options..." className="h-9" />
@ -98,7 +113,7 @@ export default function Dropdown({
</CommandGroup> </CommandGroup>
</CommandList> </CommandList>
</Command> </Command>
</PopoverContentWithoutPortal> </PopoverContentDropdown>
</Popover> </Popover>
</> </>
) : ( ) : (

View file

@ -2,7 +2,7 @@ import { gradients } from "../../utils/styleUtils";
export default function GradientChooserComponent({ value, onChange }) { export default function GradientChooserComponent({ value, onChange }) {
return ( 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) => ( {gradients.map((gradient, idx) => (
<div <div
onClick={() => { onClick={() => {

View file

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

View file

@ -33,6 +33,8 @@ export default function InputComponent({
optionsIcon = "ChevronsUpDown", optionsIcon = "ChevronsUpDown",
selectedOption, selectedOption,
setSelectedOption, setSelectedOption,
selectedOptions = [],
setSelectedOptions,
options = [], options = [],
optionsPlaceholder = "Search options...", optionsPlaceholder = "Search options...",
optionsButton, optionsButton,
@ -101,13 +103,18 @@ export default function InputComponent({
value={ value={
(selectedOption !== "" || !onChange) && setSelectedOption (selectedOption !== "" || !onChange) && setSelectedOption
? selectedOption ? selectedOption
: (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions
? selectedOptions?.join(", ")
: value : value
} }
autoFocus={autoFocus} autoFocus={autoFocus}
disabled={disabled} disabled={disabled}
onClick={() => { onClick={() => {
(selectedOption !== "" || !onChange) && (((selectedOption !== "" || !onChange) &&
setSelectedOption && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)) &&
setShowOptions(true); setShowOptions(true);
}} }}
required={required} required={required}
@ -119,9 +126,11 @@ export default function InputComponent({
? " text-clip password " ? " text-clip password "
: "", : "",
editNode ? " input-edit-node " : "", editNode ? " input-edit-node " : "",
password && setSelectedOption ? "pr-[62.9px]" : "", password && (setSelectedOption || setSelectedOptions)
(!password && setSelectedOption) || ? "pr-[62.9px]"
(password && !setSelectedOption) : "",
(!password && (setSelectedOption || setSelectedOptions)) ||
(password && !(setSelectedOption || setSelectedOptions))
? "pr-8" ? "pr-8"
: "", : "",
@ -164,7 +173,10 @@ export default function InputComponent({
> >
<Command <Command
filter={(value, search) => { 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 1; // ensures items arent filtered
return 0; return 0;
}} }}
@ -184,7 +196,15 @@ export default function InputComponent({
? "" ? ""
: currentValue : 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"> <div className="group flex w-full items-center justify-between">
@ -192,7 +212,8 @@ export default function InputComponent({
<div <div
className={cn( className={cn(
"relative mr-2 h-4 w-4", "relative mr-2 h-4 w-4",
selectedOption === option selectedOption === option ||
selectedOptions?.includes(option)
? "opacity-100" ? "opacity-100"
: "opacity-0" : "opacity-0"
)} )}
@ -228,12 +249,16 @@ export default function InputComponent({
<div <div
className={cn( className={cn(
"pointer-events-auto absolute inset-y-0 h-full w-full cursor-pointer", "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" : "hidden"
)} )}
onClick={ onClick={
(selectedOption !== "" || !onChange) && setSelectedOption ((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)
? (e) => { ? (e) => {
setShowOptions((old) => !old); setShowOptions((old) => !old);
e.preventDefault(); e.preventDefault();
@ -245,7 +270,7 @@ export default function InputComponent({
</> </>
)} )}
{setSelectedOption && ( {(setSelectedOption || setSelectedOptions) && (
<span <span
className={cn( className={cn(
password && selectedOption === "" ? "right-8" : "right-0", password && selectedOption === "" ? "right-8" : "right-0",

View file

@ -3,7 +3,6 @@ import { deleteGlobalVariable } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal"; import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables"; import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { ResponseErrorDetailAPI } from "../../types/api";
import { InputGlobalComponentType } from "../../types/components"; import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton"; import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton";
@ -24,6 +23,9 @@ export default function InputGlobalComponent({
); );
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const unavaliableFields = useGlobalVariablesStore(
(state) => state.unavaliableFields
);
const removeGlobalVariable = useGlobalVariablesStore( const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable (state) => state.removeGlobalVariable
); );
@ -35,16 +37,32 @@ export default function InputGlobalComponent({
!globalVariablesEntries.includes(data.node?.template[name].value) && !globalVariablesEntries.includes(data.node?.template[name].value) &&
data.node?.template[name].load_from_db data.node?.template[name].load_from_db
) { ) {
setTimeout(() => {
onChange(""); onChange("");
setDb(false); setDb(false);
}, 100);
} }
}, [globalVariablesEntries]); }, [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); const id = getVariableId(key);
if (id !== undefined) { if (id !== undefined) {
deleteGlobalVariable(id) await deleteGlobalVariable(id)
.then((_) => { .then(() => {
removeGlobalVariable(key); removeGlobalVariable(key);
if ( if (
data?.node?.template[name].value === key && data?.node?.template[name].value === key &&
@ -54,11 +72,10 @@ export default function InputGlobalComponent({
setDb(false); setDb(false);
} }
}) })
.catch((error) => { .catch(() => {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({ setErrorData({
title: "Error deleting variable", title: "Error deleting variable",
list: [responseError.response.data.detail ?? "Unknown error"], list: [cn("ID not found for variable: ", key)],
}); });
}); });
} else { } else {
@ -117,7 +134,8 @@ export default function InputGlobalComponent({
</DeleteConfirmationModal> </DeleteConfirmationModal>
)} )}
selectedOption={ 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 ? 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<{ export async function getGlobalVariables(): Promise<{
[key: string]: { id: string; type: string }; [key: string]: { id: string; type: string; default_fields: string[] };
}> { }> {
const globalVariables = {}; const globalVariables = {};
(await api.get(`${BASE_URL_API}variables/`)).data.forEach((element) => { (await api.get(`${BASE_URL_API}variables/`)).data.forEach((element) => {
globalVariables[element.name] = { globalVariables[element.name] = {
id: element.id, id: element.id,
type: element.type, type: element.type,
default_fields: element.default_fields,
}; };
}); });
return globalVariables; return globalVariables;
@ -878,20 +879,33 @@ export async function registerGlobalVariable({
name, name,
value, value,
type, type,
default_fields = [],
}: { }: {
name: string; name: string;
value: string; value: string;
type?: string; type?: string;
default_fields?: string[];
}): Promise<AxiosResponse<{ name: string; id: string; type: string }>> { }): Promise<AxiosResponse<{ name: string; id: string; type: string }>> {
return await api.post(`${BASE_URL_API}variables/`, { try {
const response = await api.post(`${BASE_URL_API}variables/`, {
name, name,
value, value,
type, type,
default_fields: default_fields,
}); });
return response;
} catch (error) {
throw error;
}
} }
export async function deleteGlobalVariable(id: string) { 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( export async function updateGlobalVariable(
@ -899,10 +913,16 @@ export async function updateGlobalVariable(
value: string, value: string,
id: string id: string
) { ) {
api.patch(`${BASE_URL_API}variables/${id}`, { try {
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
name, name,
value, value,
}); });
return response;
} catch (error) {
throw error;
}
} }
export async function getVerticesOrder( 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 FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage"; import HomePage from "./pages/MainPage";
import ComponentsComponent from "./pages/MainPage/components/components"; 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 StorePage from "./pages/StorePage";
import ViewPage from "./pages/ViewPage"; import ViewPage from "./pages/ViewPage";
import DeleteAccountPage from "./pages/deleteAccountPage"; import DeleteAccountPage from "./pages/deleteAccountPage";
@ -39,6 +42,19 @@ const Router = () => {
element={<ComponentsComponent key="components" />} element={<ComponentsComponent key="components" />}
/> />
</Route> </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 <Route
path="/store" path="/store"
element={ element={
@ -128,14 +144,6 @@ const Router = () => {
/> />
<Route path="/account"> <Route path="/account">
<Route
path="settings"
element={
<ProtectedRoute>
<ProfileSettingsPage />
</ProtectedRoute>
}
/>
<Route <Route
path="delete" path="delete"
element={ element={

View file

@ -22,6 +22,7 @@ import {
addVersionToDuplicates, addVersionToDuplicates,
createFlowComponent, createFlowComponent,
createNewFlow, createNewFlow,
extractFieldsFromComponenents,
processDataFromFlow, processDataFromFlow,
processFlows, processFlows,
} from "../utils/reactflowUtils"; } from "../utils/reactflowUtils";
@ -92,6 +93,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
); );
useTypesStore.setState((state) => ({ useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data }, data: { ...state.data, ["saved_components"]: data },
ComponentFields: extractFieldsFromComponenents({
...state.data,
["saved_components"]: data,
}),
})); }));
set({ isLoading: false }); set({ isLoading: false });
resolve(); resolve();
@ -207,6 +212,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
set({ isLoading: false }); set({ isLoading: false });
useTypesStore.setState((state) => ({ useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data }, data: { ...state.data, ["saved_components"]: data },
ComponentFields: extractFieldsFromComponenents({
...state.data,
["saved_components"]: data,
}),
})); }));
}, 200); }, 200);
// addFlowToLocalState(newFlow); // addFlowToLocalState(newFlow);
@ -229,6 +238,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
set({ isLoading: false }); set({ isLoading: false });
useTypesStore.setState((state) => ({ useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data }, data: { ...state.data, ["saved_components"]: data },
ComponentFields: extractFieldsFromComponenents({
...state.data,
["saved_components"]: data,
}),
})); }));
// Return the id // Return the id
@ -258,6 +271,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
set({ isLoading: false }); set({ isLoading: false });
useTypesStore.setState((state) => ({ useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data }, data: { ...state.data, ["saved_components"]: data },
ComponentFields: extractFieldsFromComponenents({
...state.data,
["saved_components"]: data,
}),
})); }));
resolve(); resolve();
}); });

View file

@ -1,30 +1,45 @@
import { create } from "zustand"; import { create } from "zustand";
import { GlobalVariablesStore } from "../types/zustand/globalVariables"; import { GlobalVariablesStore } from "../types/zustand/globalVariables";
import { getUnavailableFields } from "../utils/utils";
export const useGlobalVariablesStore = create<GlobalVariablesStore>( export const useGlobalVariablesStore = create<GlobalVariablesStore>(
(set, get) => ({ (set, get) => ({
unavaliableFields: {},
setUnavaliableFields: (fields) => {
set({ unavaliableFields: fields });
},
removeUnavaliableField: (field) => {
const newFields = get().unavaliableFields;
delete newFields[field];
set({ unavaliableFields: newFields });
},
globalVariablesEntries: [], globalVariablesEntries: [],
globalVariables: {}, globalVariables: {},
setGlobalVariables: (variables) => { setGlobalVariables: (variables) => {
set({ set({
globalVariables: variables, globalVariables: variables,
globalVariablesEntries: Object.keys(variables), globalVariablesEntries: Object.keys(variables),
unavaliableFields: getUnavailableFields(variables),
}); });
}, },
addGlobalVariable: (name, id, type) => { addGlobalVariable: (name, id, type, default_fields) => {
const data = { id, type }; const data = { id, type, default_fields };
const newVariables = { ...get().globalVariables, [name]: data }; const newVariables = { ...get().globalVariables, [name]: data };
set({ set({
globalVariables: newVariables, globalVariables: newVariables,
globalVariablesEntries: Object.keys(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 }; const newVariables = { ...get().globalVariables };
delete newVariables[name]; delete newVariables[name];
set({ set({
globalVariables: newVariables, globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables), globalVariablesEntries: Object.keys(newVariables),
unavaliableFields: getUnavailableFields(newVariables),
}); });
}, },
getVariableId: (name) => { getVariableId: (name) => {

View file

@ -2,11 +2,22 @@ import { create } from "zustand";
import { getAll } from "../controllers/API"; import { getAll } from "../controllers/API";
import { APIDataType } from "../types/api"; import { APIDataType } from "../types/api";
import { TypesStoreType } from "../types/zustand/types"; import { TypesStoreType } from "../types/zustand/types";
import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils"; import {
extractFieldsFromComponenents,
templatesGenerator,
typesGenerator,
} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore"; import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore"; import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({ export const useTypesStore = create<TypesStoreType>((set, get) => ({
ComponentFields: new Set(),
setComponentFields: (fields) => {
set({ ComponentFields: fields });
},
addComponentField: (field) => {
set({ ComponentFields: get().ComponentFields.add(field) });
},
types: {}, types: {},
templates: {}, templates: {},
data: {}, data: {},
@ -21,6 +32,10 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
set((old) => ({ set((old) => ({
types: typesGenerator(data), types: typesGenerator(data),
data: { ...old.data, ...data }, data: { ...old.data, ...data },
ComponentFields: extractFieldsFromComponenents({
...old.data,
...data,
}),
templates: templatesGenerator(data), templates: templatesGenerator(data),
})); }));
setLoading(false); setLoading(false);
@ -43,5 +58,6 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => { setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => {
let newChange = typeof change === "function" ? change(get().data) : change; let newChange = typeof change === "function" ? change(get().data) : change;
set({ data: newChange }); 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; optionButton?: (option: string) => ReactElement;
selectedOption?: string; selectedOption?: string;
setSelectedOption?: (value: string) => void; setSelectedOption?: (value: string) => void;
selectedOptions?: string[];
setSelectedOptions?: (value: string[]) => void;
}; };
export type ToggleComponentType = { export type ToggleComponentType = {
enabled: boolean; enabled: boolean;
@ -45,6 +47,7 @@ export type DropDownComponentType = {
onSelect: (value: string) => void; onSelect: (value: string) => void;
editNode?: boolean; editNode?: boolean;
id?: string; id?: string;
children?: ReactNode;
}; };
export type ParameterComponentType = { export type ParameterComponentType = {
data: NodeDataType; data: NodeDataType;
@ -72,15 +75,6 @@ export type InputListComponentType = {
playgroundDisabled?: boolean; playgroundDisabled?: boolean;
}; };
export type InputGlobalComponentType = {
disabled: boolean;
onChange: (value: string) => void;
setDb: (value: boolean) => void;
name: string;
data: NodeDataType;
editNode?: boolean;
};
export type KeyPairListComponentType = { export type KeyPairListComponentType = {
value: any; value: any;
onChange: (value: Object[]) => void; onChange: (value: Object[]) => void;

View file

@ -1,10 +1,31 @@
export type GlobalVariablesStore = { export type GlobalVariablesStore = {
globalVariablesEntries: Array<string>; globalVariablesEntries: Array<string>;
globalVariables: { [name: string]: { id: string; type?: string } }; globalVariables: {
setGlobalVariables: (variables: { [name: string]: {
[name: string]: { id: string; type?: string }; id: string;
}) => void; type?: string;
addGlobalVariable: (name: string, id: string, type?: string) => void; default_fields?: string[];
removeGlobalVariable: (name: string) => void; value?: string;
getVariableId: (name: string) => string | undefined; };
};
setGlobalVariables: (variables: {
[name: string]: {
id: string;
type?: string;
default_fields?: string[];
value?: 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; data: APIDataType;
setData: (newState: {}) => void; setData: (newState: {}) => void;
getTypes: () => Promise<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( export function downloadFlow(
flow: FlowType, flow: FlowType,
flowName: string, flowName: string,

View file

@ -5,6 +5,7 @@ import {
ArrowUpToLine, ArrowUpToLine,
Bell, Bell,
Binary, Binary,
Blocks,
BookMarked, BookMarked,
BookmarkPlus, BookmarkPlus,
Bot, Bot,
@ -67,6 +68,7 @@ import {
Home, Home,
Info, Info,
Key, Key,
Keyboard,
Laptop2, Laptop2,
Layers, Layers,
Link, Link,
@ -86,6 +88,7 @@ import {
MoreHorizontal, MoreHorizontal,
Network, Network,
Package2, Package2,
Palette,
Paperclip, Paperclip,
Pencil, Pencil,
PencilLine, PencilLine,
@ -108,6 +111,7 @@ import {
Share2, Share2,
Shield, Shield,
Sliders, Sliders,
SlidersHorizontal,
Snowflake, Snowflake,
Sparkles, Sparkles,
Square, Square,
@ -376,6 +380,7 @@ export const nodeIconsLucide: iconsType = {
tools: Hammer, tools: Hammer,
advanced: Laptop2, advanced: Laptop2,
chat: MessageCircle, chat: MessageCircle,
Keyboard: Keyboard,
embeddings: Binary, embeddings: Binary,
saved_components: GradientSave, saved_components: GradientSave,
documentloaders: Paperclip, documentloaders: Paperclip,
@ -414,6 +419,9 @@ export const nodeIconsLucide: iconsType = {
MoonIcon, MoonIcon,
Bell, Bell,
ChevronLeft, ChevronLeft,
SlidersHorizontal,
Palette,
Blocks,
ChevronDown, ChevronDown,
ArrowLeft, ArrowLeft,
Shield, Shield,

View file

@ -91,6 +91,20 @@ export function toTitleCase(
.join(" "); .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 const upperCaseWords: string[] = ["llm", "uri"];
export function checkUpperWords(str: string): string { export function checkUpperWords(str: string): string {
const words = str.split(" ").map((word) => { const words = str.split(" ").map((word) => {