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
tests:
poetry run pytest tests --instafail $(args)
# Use like:
poetry run pytest tests --instafail -ra -n auto -m "not api_key_required" $(args)
format:
poetry run ruff check . --fix

View file

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

View file

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

View file

@ -8,7 +8,7 @@ from sqlmodel import Session, select
from langflow.graph.schema import RunOutputs
from langflow.schema.schema import INPUT_FIELD_NAME, Record
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:
from langflow.graph.graph.base import Graph
@ -88,7 +88,9 @@ async def run_flow(
inputs_components.append(input_dict.get("components", []))
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(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ export default function SettingsPage(): JSX.Element {
icon: (
<ForwardedIconComponent
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: (
<ForwardedIconComponent
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",
href: "/settings/shortcuts",
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."
>
<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} />
</aside>
<div className="h-full w-full flex-1 pb-8">

View file

@ -41,12 +41,23 @@ import {
} from "../../../../types/components";
import { gradients } from "../../../../utils/styleUtils";
import { useStoreStore } from "../../../../stores/storeStore";
import { useParams } from "react-router-dom";
export default function GeneralPage() {
const setCurrentFlowId = useFlowsManagerStore(
(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>(
CONTROL_PATCH_USER_STATE,
);
@ -272,9 +283,9 @@ export default function GeneralPage() {
handleSaveKey();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<Card x-chunk="dashboard-04-chunk-2" id="api">
<CardHeader>
<CardTitle>API Key</CardTitle>
<CardTitle>Store API Key</CardTitle>
<CardDescription>
{(hasApiKey && !validApiKey
? INVALID_API_KEY

View file

@ -180,24 +180,21 @@ export default function StorePage(): JSX.Element {
title={STORE_TITLE}
description={STORE_DESC}
button={
<>
{StoreApiKeyModal && (
<StoreApiKeyModal disabled={loading}>
<Button
data-testid="api-key-button-store"
disabled={loading}
className={cn(
`${!validApiKey ? "animate-pulse border-error" : ""}`,
loading ? "cursor-not-allowed" : "",
)}
variant="primary"
>
<IconComponent name="Key" className="mr-2 w-4" />
API Key
</Button>
</StoreApiKeyModal>
<Button
data-testid="api-key-button-store"
disabled={loading}
className={cn(
`${!validApiKey ? "animate-pulse border-error" : ""}`,
loading ? "cursor-not-allowed" : "",
)}
</>
variant="primary"
onClick={() => {
navigate("/settings/general/api");
}}
>
<IconComponent name="Key" className="mr-2 w-4" />
API Key
</Button>
}
>
<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 path="global-variables" element={<GlobalVariablesPage />} />
<Route path="general" element={<GeneralPage />} />
<Route path="general/:scrollId?" element={<GeneralPage />} />
<Route path="shortcuts" element={<ShortcutsPage />} />
</Route>
<Route
@ -80,17 +80,14 @@ const Router = () => {
}
/>
<Route path="/playground/:id/">
element=
{
<Route
path=""
element={
<ProtectedRoute>
<PlaygroundPage />
</ProtectedRoute>
}
/>
}
<Route
path=""
element={
<ProtectedRoute>
<PlaygroundPage />
</ProtectedRoute>
}
/>
</Route>
<Route path="/flow/:id/">
<Route

View file

@ -4,6 +4,7 @@ from uuid import UUID, uuid4
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from langflow.custom.directory_reader.directory_reader import DirectoryReader
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
@pytest.mark.api_key_required
def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
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
@pytest.mark.api_key_required
def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
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
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key):
invalid_flow_id = uuid4()
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"]
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
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
@pytest.mark.api_key_required
def test_run_flow_with_session_id(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
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"]
@pytest.mark.api_key_required
def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]