Merge branch 'fix/minor_bugs' of https://github.com/langflow-ai/langflow into fix/minor_bugs

This commit is contained in:
cristhianzl 2024-06-04 15:33:29 -03:00
commit e6c65aff0b
16 changed files with 114 additions and 77 deletions

View file

@ -48,8 +48,8 @@ coverage:
# allow passing arguments to pytest # allow passing arguments to pytest
tests: tests:
poetry run pytest tests --instafail $(args) poetry run pytest tests --instafail -ra -n auto -m "not api_key_required" $(args)
# Use like:
format: format:
poetry run ruff check . --fix poetry run ruff check . --fix

View file

@ -14,8 +14,8 @@ Its intuitive interface allows for easy manipulation of AI building blocks, enab
<ZoomableImage <ZoomableImage
alt="Docusaurus themed image" alt="Docusaurus themed image"
sources={{ sources={{
light: "img/new_langflow_demo.gif", light: "img/langflow_basic_howto.gif",
dark: "img/new_langflow_demo.gif", dark: "img/langflow_basic_howto.gif",
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
/> />

View file

@ -140,7 +140,7 @@ testpaths = ["tests", "integration"]
console_output_style = "progress" console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"] filterwarnings = ["ignore::DeprecationWarning"]
log_cli = true log_cli = true
markers = ["async_test"] markers = ["async_test", "api_key_required"]
[tool.ruff] [tool.ruff]

View file

@ -8,7 +8,7 @@ from sqlmodel import Session, select
from langflow.graph.schema import RunOutputs from langflow.graph.schema import RunOutputs
from langflow.schema.schema import INPUT_FIELD_NAME, Record from langflow.schema.schema import INPUT_FIELD_NAME, Record
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.deps import get_session, session_scope from langflow.services.deps import get_session, get_settings_service, session_scope
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
@ -88,7 +88,9 @@ async def run_flow(
inputs_components.append(input_dict.get("components", [])) inputs_components.append(input_dict.get("components", []))
types.append(input_dict.get("type", "chat")) types.append(input_dict.get("type", "chat"))
return await graph.arun(inputs_list, inputs_components=inputs_components, types=types) fallback_to_env_vars = get_settings_service().settings.fallback_to_env_var
return await graph.arun(inputs_list, inputs_components=inputs_components, types=types, fallback_to_env_vars=fallback_to_env_vars)
def generate_function_for_flow( def generate_function_for_flow(

View file

@ -8,6 +8,7 @@ from langflow.graph.schema import RunOutputs
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.schema.graph import InputValue, Tweaks from langflow.schema.graph import InputValue, Tweaks
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.deps import get_settings_service
from langflow.services.session.service import SessionService from langflow.services.session.service import SessionService
if TYPE_CHECKING: if TYPE_CHECKING:
@ -49,6 +50,8 @@ async def run_graph_internal(
inputs_list.append({INPUT_FIELD_NAME: input_value_request.input_value}) inputs_list.append({INPUT_FIELD_NAME: input_value_request.input_value})
types.append(input_value_request.type) types.append(input_value_request.type)
fallback_to_env_vars = get_settings_service().settings.fallback_to_env_var
run_outputs = await graph.arun( run_outputs = await graph.arun(
inputs_list, inputs_list,
components, components,
@ -56,6 +59,7 @@ async def run_graph_internal(
outputs or [], outputs or [],
stream=stream, stream=stream,
session_id=session_id_str or "", session_id=session_id_str or "",
fallback_to_env_vars=fallback_to_env_vars
) )
if session_id_str and session_service: if session_id_str and session_service:
await session_service.update_session(session_id_str, (graph, artifacts)) await session_service.update_session(session_id_str, (graph, artifacts))

View file

@ -78,6 +78,7 @@ class Settings(BaseSettings):
langchain_cache: str = "InMemoryCache" langchain_cache: str = "InMemoryCache"
load_flows_path: Optional[str] = None load_flows_path: Optional[str] = None
# Redis # Redis
redis_host: str = "localhost" redis_host: str = "localhost"
redis_port: int = 6379 redis_port: int = 6379

View file

@ -123,7 +123,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
type="text" type="text"
name="endpoint_name" name="endpoint_name"
value={endpointName ?? ""} value={endpointName ?? ""}
placeholder="An alternative name for the run endpoint" placeholder="An alternative name to run the endpoint"
maxLength={maxLength} maxLength={maxLength}
id="endpoint_name" id="endpoint_name"
onDoubleClickCapture={(event) => { onDoubleClickCapture={(event) => {

View file

@ -1,6 +1,7 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import { buttonVariants } from "../../../ui/button"; import { buttonVariants } from "../../../ui/button";
import ForwardedIconComponent from "../../../genericIconComponent";
type SideBarButtonsComponentProps = { type SideBarButtonsComponentProps = {
items: { items: {
@ -16,7 +17,7 @@ const SideBarButtonsComponent = ({
pathname, pathname,
}: SideBarButtonsComponentProps) => { }: SideBarButtonsComponentProps) => {
return ( return (
<> <div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
{items.map((item) => ( {items.map((item) => (
<Link to={item.href!}> <Link to={item.href!}>
<div <div
@ -27,14 +28,17 @@ const SideBarButtonsComponent = ({
pathname === item.href pathname === item.href
? "border border-border bg-muted hover:bg-muted" ? "border border-border bg-muted hover:bg-muted"
: "border border-transparent hover:border-border hover:bg-transparent", : "border border-transparent hover:border-border hover:bg-transparent",
"w-full justify-start gap-2", "flex w-full shrink-0 justify-start gap-4",
)} )}
> >
{item.icon}
<span className="block max-w-full truncate opacity-100">
{item.title} {item.title}
</span>
</div> </div>
</Link> </Link>
))} ))}
</> </div>
); );
}; };
export default SideBarButtonsComponent; export default SideBarButtonsComponent;

View file

@ -175,11 +175,11 @@ const SideBarFoldersButtonsComponent = ({
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
}} }}
className="flex w-full items-center gap-2" className="flex w-full items-center gap-4"
> >
<IconComponent <IconComponent
name={"folder"} name={"folder"}
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100" className="w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
/> />
{editFolderName?.edit ? ( {editFolderName?.edit ? (
<div> <div>
@ -265,7 +265,6 @@ const SideBarFoldersButtonsComponent = ({
{item.name} {item.name}
</span> </span>
)} )}
<div className="flex-1" />
{index > 0 && ( {index > 0 && (
<Button <Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]" className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
@ -304,7 +303,8 @@ const SideBarFoldersButtonsComponent = ({
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}} }}
variant={"ghost"} size="none"
variant="none"
> >
<IconComponent <IconComponent
name={"Download"} name={"Download"}

View file

@ -41,9 +41,12 @@ export default function SidebarNav({
return ( return (
<nav className={cn(className)} {...props}> <nav className={cn(className)} {...props}>
<HorizontalScrollFadeComponent> <HorizontalScrollFadeComponent>
{items.length > 0 ? (
<SideBarButtonsComponent items={items} pathname={pathname} /> <SideBarButtonsComponent items={items} pathname={pathname} />
) : (
{!loadingFolders && folders?.length > 0 && isFolderPath && ( !loadingFolders &&
folders?.length > 0 &&
isFolderPath && (
<SideBarFoldersButtonsComponent <SideBarFoldersButtonsComponent
folders={folders} folders={folders}
pathname={pathname} pathname={pathname}
@ -51,6 +54,7 @@ export default function SidebarNav({
handleEditFolder={handleEditFolder} handleEditFolder={handleEditFolder}
handleDeleteFolder={handleDeleteFolder} handleDeleteFolder={handleDeleteFolder}
/> />
)
)} )}
</HorizontalScrollFadeComponent> </HorizontalScrollFadeComponent>
</nav> </nav>

View file

@ -20,6 +20,7 @@ const buttonVariants = cva(
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5", "border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary", link: "underline-offset-4 hover:underline text-primary",
none: "",
}, },
size: { size: {
default: "h-10 py-2 px-4", default: "h-10 py-2 px-4",
@ -27,6 +28,7 @@ const buttonVariants = cva(
xs: "py-0.5 px-3 rounded-md", xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md", lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md", icon: "py-1 px-1 rounded-md",
none: "",
}, },
}, },
defaultVariants: { defaultVariants: {
@ -61,24 +63,29 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
newChildren = toTitleCase(children); newChildren = toTitleCase(children);
} }
return ( return (
<Comp className={"relative"} ref={ref} {...props}> <>
<div <Comp
className={cn( className={cn(buttonVariants({ variant, size, className }))}
loading ? "opacity-100" : "opacity-0", ref={ref}
"absolute self-center", {...props}
)}
>
<ForwardedIconComponent name={"Loader2"} className={"animate-spin"} />
</div>
<div
className={cn(
loading ? "opacity-0" : "opacity-100",
buttonVariants({ variant, size, className }),
)}
> >
{loading ? (
<span className={cn("relative")}>
<span className={loading ? "invisible" : "hidden"}>
{newChildren} {newChildren}
</div> </span>
<span className="absolute inset-0 flex items-center justify-center">
<ForwardedIconComponent
name={"Loader2"}
className={"animate-spin"}
/>
</span>
</span>
) : (
newChildren
)}
</Comp> </Comp>
</>
); );
}, },
); );

View file

@ -21,7 +21,7 @@ export default function SettingsPage(): JSX.Element {
icon: ( icon: (
<ForwardedIconComponent <ForwardedIconComponent
name="SlidersHorizontal" name="SlidersHorizontal"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]" className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/> />
), ),
}, },
@ -32,7 +32,7 @@ export default function SettingsPage(): JSX.Element {
icon: ( icon: (
<ForwardedIconComponent <ForwardedIconComponent
name="Globe" name="Globe"
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]" className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/> />
), ),
}, },
@ -40,7 +40,10 @@ export default function SettingsPage(): JSX.Element {
title: "Shortcuts", title: "Shortcuts",
href: "/settings/shortcuts", href: "/settings/shortcuts",
icon: ( icon: (
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" /> <ForwardedIconComponent
name="Keyboard"
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
), ),
}, },
]; ];
@ -50,7 +53,7 @@ export default function SettingsPage(): JSX.Element {
description="Manage the general settings for Langflow." 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"> <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"> <aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]">
<SidebarNav items={sidebarNavItems} /> <SidebarNav items={sidebarNavItems} />
</aside> </aside>
<div className="h-full w-full flex-1 pb-8"> <div className="h-full w-full flex-1 pb-8">

View file

@ -41,12 +41,23 @@ import {
} from "../../../../types/components"; } from "../../../../types/components";
import { gradients } from "../../../../utils/styleUtils"; import { gradients } from "../../../../utils/styleUtils";
import { useStoreStore } from "../../../../stores/storeStore"; import { useStoreStore } from "../../../../stores/storeStore";
import { useParams } from "react-router-dom";
export default function GeneralPage() { export default function GeneralPage() {
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId, (state) => state.setCurrentFlowId,
); );
const { scrollId } = useParams();
useEffect(() => {
const element = document.getElementById(scrollId ?? "null");
if (element) {
// 👇 Will scroll smoothly to the top of the next section
element.scrollIntoView({ behavior: "smooth" });
}
}, [scrollId]);
const [inputState, setInputState] = useState<patchUserInputStateType>( const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE, CONTROL_PATCH_USER_STATE,
); );
@ -272,9 +283,9 @@ export default function GeneralPage() {
handleSaveKey(); handleSaveKey();
}} }}
> >
<Card x-chunk="dashboard-04-chunk-2"> <Card x-chunk="dashboard-04-chunk-2" id="api">
<CardHeader> <CardHeader>
<CardTitle>API Key</CardTitle> <CardTitle>Store API Key</CardTitle>
<CardDescription> <CardDescription>
{(hasApiKey && !validApiKey {(hasApiKey && !validApiKey
? INVALID_API_KEY ? INVALID_API_KEY

View file

@ -180,9 +180,6 @@ export default function StorePage(): JSX.Element {
title={STORE_TITLE} title={STORE_TITLE}
description={STORE_DESC} description={STORE_DESC}
button={ button={
<>
{StoreApiKeyModal && (
<StoreApiKeyModal disabled={loading}>
<Button <Button
data-testid="api-key-button-store" data-testid="api-key-button-store"
disabled={loading} disabled={loading}
@ -191,13 +188,13 @@ export default function StorePage(): JSX.Element {
loading ? "cursor-not-allowed" : "", loading ? "cursor-not-allowed" : "",
)} )}
variant="primary" variant="primary"
onClick={() => {
navigate("/settings/general/api");
}}
> >
<IconComponent name="Key" className="mr-2 w-4" /> <IconComponent name="Key" className="mr-2 w-4" />
API Key API Key
</Button> </Button>
</StoreApiKeyModal>
)}
</>
} }
> >
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">

View file

@ -56,7 +56,7 @@ const Router = () => {
> >
<Route index element={<Navigate replace to={"general"} />} /> <Route index element={<Navigate replace to={"general"} />} />
<Route path="global-variables" element={<GlobalVariablesPage />} /> <Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="general" element={<GeneralPage />} /> <Route path="general/:scrollId?" element={<GeneralPage />} />
<Route path="shortcuts" element={<ShortcutsPage />} /> <Route path="shortcuts" element={<ShortcutsPage />} />
</Route> </Route>
<Route <Route
@ -80,8 +80,6 @@ const Router = () => {
} }
/> />
<Route path="/playground/:id/"> <Route path="/playground/:id/">
element=
{
<Route <Route
path="" path=""
element={ element={
@ -90,7 +88,6 @@ const Router = () => {
</ProtectedRoute> </ProtectedRoute>
} }
/> />
}
</Route> </Route>
<Route path="/flow/:id/"> <Route path="/flow/:id/">
<Route <Route

View file

@ -4,6 +4,7 @@ from uuid import UUID, uuid4
import pytest import pytest
from fastapi import status from fastapi import status
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from langflow.custom.directory_reader.directory_reader import DirectoryReader from langflow.custom.directory_reader.directory_reader import DirectoryReader
from langflow.services.deps import get_settings_service from langflow.services.deps import get_settings_service
@ -632,6 +633,7 @@ def test_successful_run_with_input_type_any(client, starter_project, created_api
assert all([output.get("results").get("result") == "value1" for output in any_input_outputs]), any_input_outputs assert all([output.get("results").get("result") == "value1" for output in any_input_outputs]), any_input_outputs
@pytest.mark.api_key_required
def test_run_with_inputs_and_outputs(client, starter_project, created_api_key): def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"] flow_id = starter_project["id"]
@ -659,6 +661,7 @@ def test_invalid_flow_id(client, created_api_key):
# Check if the error detail is as expected # Check if the error detail is as expected
@pytest.mark.api_key_required
def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key): def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"] flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
@ -676,6 +679,7 @@ def test_run_flow_with_caching_success(client: TestClient, starter_project, crea
assert "session_id" in data assert "session_id" in data
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key): def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key):
invalid_flow_id = uuid4() invalid_flow_id = uuid4()
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
@ -687,6 +691,7 @@ def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_k
assert f"Flow identifier {invalid_flow_id} not found" in data["detail"] assert f"Flow identifier {invalid_flow_id} not found" in data["detail"]
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key): def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"] flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
@ -695,6 +700,7 @@ def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
@pytest.mark.api_key_required
def test_run_flow_with_session_id(client, starter_project, created_api_key): def test_run_flow_with_session_id(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"] flow_id = starter_project["id"]
@ -726,6 +732,7 @@ def test_run_flow_with_invalid_session_id(client, starter_project, created_api_k
assert f"Session {payload['session_id']} not found" in data["detail"] assert f"Session {payload['session_id']} not found" in data["detail"]
@pytest.mark.api_key_required
def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key): def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"] flow_id = starter_project["id"]