This commit is contained in:
Cristhian Zanforlin Lousa 2023-08-31 16:07:53 -03:00
commit 1b317ee4c9
31 changed files with 574 additions and 403 deletions

0
.githooks/pre-commit Normal file → Executable file
View file

13
poetry.lock generated
View file

@ -7041,6 +7041,17 @@ files = [
{file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"}, {file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"},
] ]
[[package]]
name = "types-pywin32"
version = "306.0.0.4"
description = "Typing stubs for pywin32"
optional = false
python-versions = "*"
files = [
{file = "types-pywin32-306.0.0.4.tar.gz", hash = "sha256:ae4bbec80d535053236d4bebedf55f58dee89cf5883d277f0fa89e857f3ff337"},
{file = "types_pywin32-306.0.0.4-py3-none-any.whl", hash = "sha256:f76a343ed6933008af85e158063963f923e54f2f461e697b2929b4178c7b77a1"},
]
[[package]] [[package]]
name = "types-pyyaml" name = "types-pyyaml"
version = "6.0.12.11" version = "6.0.12.11"
@ -7773,4 +7784,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "c877b4d713eef71815d858d30976ab21c42e5eadcc2df8159e940e03323681ee" content-hash = "a3a506d483c2db7169a9790090095d1764aa5be223d135c6fc3fc2768dfef36c"

View file

@ -103,6 +103,7 @@ types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13" types-passlib = "^1.7.7.13"
pytest-mock = "^3.11.1" pytest-mock = "^3.11.1"
pytest-xdist = "^3.3.1" pytest-xdist = "^3.3.1"
types-pywin32 = "^306.0.0.4"
[tool.poetry.extras] [tool.poetry.extras]

View file

@ -356,7 +356,7 @@ def superuser(
with session_getter(db_manager) as session: with session_getter(db_manager) as session:
from langflow.services.auth.utils import create_super_user from langflow.services.auth.utils import create_super_user
if create_super_user(session, username, password): if create_super_user(db=session, username=username, password=password):
# Verify that the superuser was created # Verify that the superuser was created
from langflow.services.database.models.user.user import User from langflow.services.database.models.user.user import User

View file

@ -61,14 +61,13 @@ async def chat(
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
except Exception as exc: except Exception as exc:
logger.error(f"Error in chat websocket: {exc}") logger.error(f"Error in chat websocket: {exc}")
if isinstance(exc, HTTPException): messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
exc = exc.detail
if "Could not validate credentials" in str(exc): if "Could not validate credentials" in str(exc):
await websocket.close( await websocket.close(
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
) )
else: else:
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
@router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201) @router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201)

View file

@ -43,7 +43,9 @@ def add_user(
db.refresh(new_user) db.refresh(new_user)
except IntegrityError as e: except IntegrityError as e:
db.rollback() db.rollback()
raise HTTPException(status_code=400, detail="This username is unavailable.") from e raise HTTPException(
status_code=400, detail="This username is unavailable."
) from e
return new_user return new_user

View file

@ -10,7 +10,7 @@ from langflow.api import router
from langflow.interface.utils import setup_llm_caching from langflow.interface.utils import setup_llm_caching
from langflow.services.database.utils import initialize_database from langflow.services.database.utils import initialize_database
from langflow.services.manager import initialize_services from langflow.services.manager import initialize_services, teardown_services
from langflow.utils.logger import configure from langflow.utils.logger import configure
@ -40,6 +40,7 @@ def create_app():
app.on_event("startup")(initialize_services) app.on_event("startup")(initialize_services)
app.on_event("startup")(initialize_database) app.on_event("startup")(initialize_database)
app.on_event("startup")(setup_llm_caching) app.on_event("startup")(setup_llm_caching)
app.on_event("shutdown")(teardown_services)
return app return app

View file

@ -37,7 +37,12 @@ async def api_key_security(
result: Optional[Union[ApiKey, User]] = None result: Optional[Union[ApiKey, User]] = None
if settings_manager.auth_settings.AUTO_LOGIN: if settings_manager.auth_settings.AUTO_LOGIN:
# Get the first user # Get the first user
settings_manager.auth_settings.FIRST_SUPERUSER if not settings_manager.auth_settings.FIRST_SUPERUSER:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing first superuser credentials",
)
result = get_user_by_username( result = get_user_by_username(
db, settings_manager.auth_settings.FIRST_SUPERUSER db, settings_manager.auth_settings.FIRST_SUPERUSER
) )
@ -80,6 +85,9 @@ async def get_current_user(
if isinstance(token, Coroutine): if isinstance(token, Coroutine):
token = await token token = await token
if settings_manager.auth_settings.SECRET_KEY is None:
raise credentials_exception
try: try:
payload = jwt.decode( payload = jwt.decode(
token, token,
@ -150,22 +158,16 @@ def create_token(data: dict, expires_delta: timedelta):
def create_super_user( def create_super_user(
username: str,
password: str,
db: Session = Depends(get_session), db: Session = Depends(get_session),
username: Optional[str] = None,
password: Optional[str] = None,
) -> User: ) -> User:
settings_manager = get_settings_manager() super_user = get_user_by_username(db, username)
super_user = get_user_by_username(
db, username or settings_manager.auth_settings.FIRST_SUPERUSER
)
if not super_user: if not super_user:
super_user = User( super_user = User(
username=username or settings_manager.auth_settings.FIRST_SUPERUSER, username=username,
password=get_password_hash( password=get_password_hash(password),
password or settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD
),
is_superuser=True, is_superuser=True,
is_active=True, is_active=True,
last_login_at=None, last_login_at=None,
@ -179,7 +181,15 @@ def create_super_user(
def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: def create_user_longterm_token(db: Session = Depends(get_session)) -> dict:
super_user = create_super_user(db) settings_manager = get_settings_manager()
username = settings_manager.auth_settings.FIRST_SUPERUSER
password = settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD
if not username or not password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing first superuser credentials",
)
super_user = create_super_user(db=db, username=username, password=password)
access_token_expires_longterm = timedelta(days=365) access_token_expires_longterm = timedelta(days=365)
access_token = create_token( access_token = create_token(

View file

@ -1,2 +1,8 @@
class Service: from abc import ABC
class Service(ABC):
name: str name: str
def teardown(self):
pass

View file

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from langflow.services.base import Service from langflow.services.base import Service
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults from langflow.services.database.utils import Result, TableResults
from langflow.services.utils import get_settings_manager from langflow.services.utils import get_settings_manager
from sqlalchemy import inspect from sqlalchemy import inspect
@ -159,3 +160,23 @@ class DatabaseManager(Service):
) )
logger.debug("Database and tables created successfully") logger.debug("Database and tables created successfully")
def teardown(self):
logger.debug("Tearing down database")
try:
settings_manager = get_settings_manager()
# remove the default superuser if auto_login is enabled
# using the FIRST_SUPERUSER to get the user
if settings_manager.auth_settings.AUTO_LOGIN:
logger.debug("Removing default superuser")
username = settings_manager.auth_settings.FIRST_SUPERUSER
with Session(self.engine) as session:
user = get_user_by_username(session, username)
session.delete(user)
session.commit()
logger.debug("Default superuser removed")
except Exception as exc:
logger.error(f"Error tearing down database: {exc}")
self.engine.dispose()

View file

@ -1,5 +1,6 @@
from langflow.services.schema import ServiceType from langflow.services.schema import ServiceType
from typing import TYPE_CHECKING, List, Optional from typing import TYPE_CHECKING, List, Optional
from langflow.utils.logger import logger
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.factory import ServiceFactory from langflow.services.factory import ServiceFactory
@ -42,6 +43,7 @@ class ServiceManager:
""" """
Create a new service given its name, handling dependencies. Create a new service given its name, handling dependencies.
""" """
logger.debug(f"Create service {service_name}")
self._validate_service_creation(service_name) self._validate_service_creation(service_name)
# Create dependencies first # Create dependencies first
@ -74,9 +76,21 @@ class ServiceManager:
Update a service by its name. Update a service by its name.
""" """
if service_name in self.services: if service_name in self.services:
logger.debug(f"Update service {service_name}")
self.services.pop(service_name, None) self.services.pop(service_name, None)
self.get(service_name) self.get(service_name)
def teardown(self):
"""
Teardown all the services.
"""
for service in self.services.values():
logger.debug(f"Teardown service {service.name}")
service.teardown()
self.services = {}
self.factories = {}
self.dependencies = {}
service_manager = ServiceManager() service_manager = ServiceManager()
@ -121,7 +135,7 @@ def initialize_session_manager():
""" """
Initialize the session manager. Initialize the session manager.
""" """
from langflow.services.session import factory as session_manager_factory from langflow.services.session import factory as session_manager_factory # type: ignore
from langflow.services.cache import factory as cache_factory from langflow.services.cache import factory as cache_factory
initialize_settings_manager() initialize_settings_manager()
@ -134,3 +148,10 @@ def initialize_session_manager():
session_manager_factory.SessionManagerFactory(), session_manager_factory.SessionManagerFactory(),
dependencies=[ServiceType.CACHE_MANAGER], dependencies=[ServiceType.CACHE_MANAGER],
) )
def teardown_services():
"""
Teardown all the services.
"""
service_manager.teardown()

View file

@ -11,10 +11,11 @@ from langflow.utils.logger import logger
class AuthSettings(BaseSettings): class AuthSettings(BaseSettings):
# Login settings # Login settings
CONFIG_DIR: str CONFIG_DIR: str
SECRET_KEY: Optional[str] = Field( SECRET_KEY: str = Field(
None, default="",
description="Secret key for JWT. If not provided, a random one will be generated.", description="Secret key for JWT. If not provided, a random one will be generated.",
env="LANGFLOW_SECRET_KEY", env="LANGFLOW_SECRET_KEY",
allow_mutation=False,
) )
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 ACCESS_TOKEN_EXPIRE_MINUTES: int = 60

View file

@ -35,5 +35,10 @@ class SettingsManager(Service):
) )
settings = Settings(**settings_dict) settings = Settings(**settings_dict)
auth_settings = AuthSettings(CONFIG_DIR=settings.CONFIG_DIR) if not settings.CONFIG_DIR:
raise ValueError("CONFIG_DIR must be set in settings")
auth_settings = AuthSettings(
CONFIG_DIR=settings.CONFIG_DIR,
)
return cls(settings, auth_settings) return cls(settings, auth_settings)

View file

@ -43,5 +43,5 @@ def write_secret_to_file(path: Path, value: str) -> None:
def read_secret_from_file(path: Path) -> str: def read_secret_from_file(path: Path) -> str:
with path.open("rb") as f: with path.open("r") as f:
return f.read() return f.read()

View file

@ -0,0 +1,67 @@
import { useState } from "react";
import { dropdownButtonPropsType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
export default function DropdownButton({
firstButtonName,
onFirstBtnClick,
options,
}: dropdownButtonPropsType): JSX.Element {
const [showOptions, setShowOptions] = useState<boolean>(false);
return (
<div>
<DropdownMenu open={showOptions}>
<DropdownMenuTrigger asChild>
<Button
variant="primary"
className="relative pr-10"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
onFirstBtnClick();
}}
>
{firstButtonName}
<div
className="absolute right-2 items-center text-muted-foreground"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{!showOptions ? (
<IconComponent
name="ChevronDown"
/>
) : (
<IconComponent name="ChevronUp" />
)}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
onPointerDownOutside={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{options.map(({ name, onBtnClick }, index) => (
<DropdownMenuItem onClick={onBtnClick} key={index}>
{name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}

View file

@ -28,7 +28,6 @@ import {
TabsList, TabsList,
TabsTrigger, TabsTrigger,
} from "../../components/ui/tabs"; } from "../../components/ui/tabs";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { codeTabsPropsType } from "../../types/components"; import { codeTabsPropsType } from "../../types/components";

View file

@ -1,4 +1,4 @@
import { cn } from "../../utils/utils" import { cn } from "../../utils/utils";
function Skeleton({ function Skeleton({
className, className,
@ -9,7 +9,7 @@ function Skeleton({
className={cn("animate-pulse rounded-md bg-border", className)} className={cn("animate-pulse rounded-md bg-border", className)}
{...props} {...props}
/> />
) );
} }
export { Skeleton } export { Skeleton };

View file

@ -25,7 +25,7 @@ const initialValue: alertContextType = {
notificationList: [], notificationList: [],
pushNotificationList: () => {}, pushNotificationList: () => {},
clearNotificationList: () => {}, clearNotificationList: () => {},
removeFromNotificationList: () => {} removeFromNotificationList: () => {},
}; };
export const alertContext = createContext<alertContextType>(initialValue); export const alertContext = createContext<alertContextType>(initialValue);

View file

@ -48,7 +48,7 @@ const TabsContextInitialValue: TabsContextType = {
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {}, downloadFlows: () => {},
uploadFlows: () => {}, uploadFlows: () => {},
uploadFlow: () => {}, uploadFlow: async () => "",
isBuilt: false, isBuilt: false,
setIsBuilt: (state: boolean) => {}, setIsBuilt: (state: boolean) => {},
hardReset: () => {}, hardReset: () => {},
@ -298,39 +298,38 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* If the file type is application/json, the file is read and parsed into a JSON object. * If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow(newProject?: boolean, file?: File) { async function uploadFlow(
newProject?: boolean,
file?: File
): Promise<String | undefined> {
let id;
if (file) { if (file) {
file.text().then((text) => { let text = await file.text();
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject); id = await addFlow(flow, newProject);
});
} else { } else {
// create a file input // create a file input
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = ".json"; input.accept = ".json";
// add a change event listener to the file input // add a change event listener to the file input
input.onchange = (e: Event) => { id = await new Promise(resolve => {
// check if the file type is application/json input.onchange = async (e: Event) => {
if ( if ((e.target as HTMLInputElement).files![0].type === "application/json") {
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const currentfile = (e.target as HTMLInputElement).files![0]; const currentfile = (e.target as HTMLInputElement).files![0];
// read the file as text let text = await currentfile.text();
currentfile.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
const flowId = await addFlow(flow, newProject);
addFlow(flow, newProject); resolve(flowId);
});
} }
}; };
// trigger the file input click event to open the file dialog // trigger the file input click event to open the file dialog
input.click(); input.click();
});
} }
return id;
} }
function uploadFlows() { function uploadFlows() {

View file

@ -8,7 +8,7 @@ import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage"; import ChatMessage from "./chatMessage";
import _, { set } from "lodash"; import _ from "lodash";
import AccordionComponent from "../../components/AccordionComponent"; import AccordionComponent from "../../components/AccordionComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import ToggleShadComponent from "../../components/toggleShadComponent"; import ToggleShadComponent from "../../components/toggleShadComponent";
@ -25,9 +25,9 @@ import { Textarea } from "../../components/ui/textarea";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API";
import { TabsState } from "../../types/tabs"; import { TabsState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils"; import { validateNodes } from "../../utils/reactflowUtils";
import { getBuildStatus } from "../../controllers/API";
export default function FormModal({ export default function FormModal({
flow, flow,
@ -156,18 +156,20 @@ export default function FormModal({
function handleOnClose(event: CloseEvent): void { function handleOnClose(event: CloseEvent): void {
if (isOpen.current) { if (isOpen.current) {
getBuildStatus(flow.id).then((response) => { getBuildStatus(flow.id)
.then((response) => {
if (response.data.built) { if (response.data.built) {
connectWS(); connectWS();
} } else {
else {
setErrorData({ setErrorData({
title: "Please build the flow again before using the chat." title: "Please build the flow again before using the chat.",
}) });
} }
}).catch((error) => { })
setErrorData({title:error.data?.detail?error.data.detail:error.message}) .catch((error) => {
setErrorData({
title: error.data?.detail ? error.data.detail : error.message,
});
}); });
setErrorData({ title: event.reason }); setErrorData({ title: event.reason });
setTimeout(() => { setTimeout(() => {
@ -186,7 +188,8 @@ export default function FormModal({
const host = isDevelopment ? "localhost:7860" : window.location.host; const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`; const chatEndpoint = `/api/v1/chat/${chatId}`;
return `${isDevelopment ? "ws" : webSocketProtocol return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`; }://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`;
} }

View file

@ -136,7 +136,11 @@ export default function GenericModal({
setSuccessData({ setSuccessData({
title: "Prompt is ready", title: "Prompt is ready",
}); });
if(JSON.stringify(apiReturn.data?.frontend_node)!==JSON.stringify({})) setNodeClass!(apiReturn.data?.frontend_node); if (
JSON.stringify(apiReturn.data?.frontend_node) !==
JSON.stringify({})
)
setNodeClass!(apiReturn.data?.frontend_node);
setModalOpen(closeModal); setModalOpen(closeModal);
setValue(inputValue); setValue(inputValue);
} }

View file

@ -4,6 +4,7 @@ import { useContext, useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent"; import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox"; import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -25,9 +26,8 @@ import {
} from "../../controllers/API"; } from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal"; import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal"; import UserManagementModal from "../../modals/UserManagementModal";
import { UserInputType } from "../../types/components";
import Header from "../../components/headerComponent";
import { Users } from "../../types/api"; import { Users } from "../../types/api";
import { UserInputType } from "../../types/components";
export default function AdminPage() { export default function AdminPage() {
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
@ -280,7 +280,8 @@ export default function AdminPage() {
</TableHeader> </TableHeader>
{!loadingUsers && ( {!loadingUsers && (
<TableBody> <TableBody>
{filterUserList.map((user:UserInputType, index) => ( {filterUserList.map(
(user: UserInputType, index) => (
<TableRow key={index}> <TableRow key={index}>
<TableCell className="truncate py-2 font-medium"> <TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}> <ShadTooltip content={user.id}>
@ -396,7 +397,10 @@ export default function AdminPage() {
handleDeleteUser(user); handleDeleteUser(user);
}} }}
> >
<ShadTooltip content="Delete" side="top"> <ShadTooltip
content="Delete"
side="top"
>
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer" className="ml-2 h-4 w-4 cursor-pointer"
@ -406,7 +410,8 @@ export default function AdminPage() {
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} )
)}
</TableBody> </TableBody>
)} )}
</Table> </Table>

View file

@ -21,8 +21,7 @@ export default function ExtraSidebar(): JSX.Element {
const { data, templates } = useContext(typesContext); const { data, templates } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } = const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext); useContext(TabsContext);
const { setSuccessData, setErrorData } = const { setSuccessData, setErrorData } = useContext(alertContext);
useContext(alertContext);
const [dataFilter, setFilterData] = useState(data); const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const isPending = tabsState[tabId]?.isPending; const isPending = tabsState[tabId]?.isPending;
@ -101,9 +100,7 @@ export default function ExtraSidebar(): JSX.Element {
<div className="side-bar-button"> <div className="side-bar-button">
{flow && flow.data && ( {flow && flow.data && (
<ApiModal flow={flow} disable={!isBuilt}> <ApiModal flow={flow} disable={!isBuilt}>
<div <div className={classNames("extra-side-bar-buttons")}>
className={classNames("extra-side-bar-buttons")}
>
<IconComponent <IconComponent
name="Code2" name="Code2"
className={ className={

View file

@ -7,6 +7,7 @@ import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import DropdownButton from "../../components/DropdownButtonComponent";
export default function HomePage(): JSX.Element { export default function HomePage(): JSX.Element {
const { const {
flows, flows,
@ -14,9 +15,12 @@ export default function HomePage(): JSX.Element {
downloadFlows, downloadFlows,
uploadFlows, uploadFlows,
addFlow, addFlow,
removeFlow, removeFlow, uploadFlow,
isLoading, isLoading,
} = useContext(TabsContext); } = useContext(TabsContext);
const dropdownOptions = [{name: "Import from JSON", onBtnClick: () => uploadFlow(true).then((id) => {
navigate("/flow/" + id);
})}]
// Set a null id // Set a null id
useEffect(() => { useEffect(() => {
@ -57,17 +61,15 @@ export default function HomePage(): JSX.Element {
<IconComponent name="Upload" className="main-page-nav-button" /> <IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection Upload Collection
</Button> </Button>
<Button <DropdownButton
variant="primary" firstButtonName="New Project"
onClick={() => { onFirstBtnClick={() => {
addFlow(null!, true).then((id) => { addFlow(null!, true).then((id) => {
navigate("/flow/" + id); navigate("/flow/" + id);
}); });
}} }}
> options={dropdownOptions}
<IconComponent name="Plus" className="main-page-nav-button" /> />
New Project
</Button>
</div> </div>
</div> </div>
<span className="main-page-description-text"> <span className="main-page-description-text">

View file

@ -19,7 +19,8 @@ export default function LoginPage(): JSX.Element {
useState<loginInputStateType>(CONTROL_LOGIN_STATE); useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { password, username } = inputState; const { password, username } = inputState;
const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext); const { login, getAuthentication, setUserData, setIsAdmin } =
useContext(AuthContext);
const navigate = useNavigate(); const navigate = useNavigate();
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
@ -130,7 +131,9 @@ export default function LoginPage(): JSX.Element {
</div> </div>
<div className="w-full"> <div className="w-full">
<Form.Submit asChild> <Form.Submit asChild>
<Button className="mr-3 mt-6 w-full" type="submit">Sign in</Button> <Button className="mr-3 mt-6 w-full" type="submit">
Sign in
</Button>
</Form.Submit> </Form.Submit>
</div> </div>
<div className="w-full"> <div className="w-full">

View file

@ -544,3 +544,9 @@ export type fetchErrorComponentType = {
message: string; message: string;
description: string; description: string;
}; };
export type dropdownButtonPropsType = {
firstButtonName: string;
onFirstBtnClick: () => void;
options: Array<{ name: string; onBtnClick: () => void; }>;
};

View file

@ -24,7 +24,7 @@ export type TabsContextType = {
uploadFlows: () => void; uploadFlows: () => void;
isBuilt: boolean; isBuilt: boolean;
setIsBuilt: (state: boolean) => void; setIsBuilt: (state: boolean) => void;
uploadFlow: (newFlow?: boolean, file?: File) => void; uploadFlow: (newFlow?: boolean, file?: File) => Promise<String | undefined>;
hardReset: () => void; hardReset: () => void;
getNodeId: (nodeType: string) => string; getNodeId: (nodeType: string) => string;
tabsState: TabsState; tabsState: TabsState;

View file

@ -5,6 +5,7 @@ import {
ChevronDown, ChevronDown,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
ChevronUp,
ChevronsLeft, ChevronsLeft,
ChevronsRight, ChevronsRight,
ChevronsUpDown, ChevronsUpDown,
@ -300,4 +301,5 @@ export const nodeIconsLucide: iconsType = {
UserCog2, UserCog2,
Key, Key,
Unplug, Unplug,
ChevronUp,
}; };

View file

@ -9,7 +9,13 @@ from langflow.services.database.models.user import UserUpdate
@pytest.fixture @pytest.fixture
def super_user(client, session): def super_user(client, session):
return create_super_user(session) settings_manager = get_settings_manager()
auth_settings = settings_manager.auth_settings
return create_super_user(
db=session,
username=auth_settings.FIRST_SUPERUSER,
password=auth_settings.FIRST_SUPERUSER_PASSWORD,
)
@pytest.fixture @pytest.fixture