diff --git a/.gitattributes b/.gitattributes
index 19bd1f10f..4b878819c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -7,10 +7,19 @@
*.h text
*.py text
*.js text
-*.ts text
*.jsx text
+*.ts text
+*.tsx text
*.md text
*.mdx text
+*.yml text
+*.yaml text
+*.xml text
+*.csv text
+*.json text
+*.sh text
+*.Dockerfile text
+Dockerfile text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
@@ -18,8 +27,8 @@
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
-*.mp4 binary
-*.svg binary
*.ico binary
*.gif binary
-*.csv binary
\ No newline at end of file
+*.mp4 binary
+*.svg binary
+*.csv binary
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
old mode 100755
new mode 100644
diff --git a/.gitignore b/.gitignore
index 3b6cfebbf..156f44394 100644
--- a/.gitignore
+++ b/.gitignore
@@ -253,3 +253,4 @@ langflow.db
.docusaurus/
/tmp/*
+src/backend/langflow/frontend/
diff --git a/Makefile b/Makefile
index e2d90d0a5..0b05ec7cf 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,12 @@ install_frontendc:
run_frontend:
cd src/frontend && npm start
+run_cli:
+ poetry run langflow --path src/frontend/build
+
+run_cli_debug:
+ poetry run langflow --path src/frontend/build --log-level debug
+
setup_devcontainer:
make init
make build_frontend
diff --git a/docker_example/README.md b/docker_example/README.md
new file mode 100644
index 000000000..9e72dc645
--- /dev/null
+++ b/docker_example/README.md
@@ -0,0 +1,9 @@
+# LangFlow Docker Running
+
+```sh
+git clone git@github.com:logspace-ai/langflow.git
+cd langflow/docker_example
+docker compose up
+```
+
+The web UI will be accessible on port [7860](http://localhost:7860/)
\ No newline at end of file
diff --git a/docs/docs/components/utilities.mdx b/docs/docs/components/utilities.mdx
index f510990ce..593864213 100644
--- a/docs/docs/components/utilities.mdx
+++ b/docs/docs/components/utilities.mdx
@@ -1,10 +1,76 @@
-import Admonition from '@theme/Admonition';
+import Admonition from "@theme/Admonition";
# Utilities
-
- We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
-
+
+ We appreciate your understanding as we polish our documentation – it may
+ contain some rough edges. Share your feedback or report issues to help us
+ improve! 🛠️📝
+
+ );
+}
diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx
index 1c780417b..6e63be50e 100644
--- a/src/frontend/src/components/headerComponent/index.tsx
+++ b/src/frontend/src/components/headerComponent/index.tsx
@@ -1,4 +1,4 @@
-import { useContext, useEffect, useState } from "react";
+import { useContext } from "react";
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
import { Link, useLocation, useNavigate } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown";
@@ -7,7 +7,6 @@ import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { darkContext } from "../../contexts/darkContext";
import { TabsContext } from "../../contexts/tabsContext";
-import { getRepoStars } from "../../controllers/API";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
@@ -44,22 +43,35 @@ export default function Header(): JSX.Element {
)}
{autoLogin === false && (
-
+
+ )}
+
+ {location.pathname === "/admin" && (
+ {
+ navigate("/");
+ }}
+ className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
+ >
+ Home
+
)}
{isAdmin && !autoLogin && location.pathname !== "/admin" && (
-
+
)}
diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts
index a235256a6..dee87dcdb 100644
--- a/src/frontend/src/constants/constants.ts
+++ b/src/frontend/src/constants/constants.ts
@@ -615,8 +615,11 @@ export function tabsArray(codes: string[], method: number) {
},
];
}
+export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
+export const FETCH_ERROR_DESCRIPION =
+ "Check if everything is working properly and try again.";
-export const BASE_URL_API = "http://localhost:7860/";
+export const BASE_URL_API = "/api/v1/";
export const SIGN_UP_SUCCESS =
"Account created! Await admin activation. ";
diff --git a/src/frontend/src/contexts/alertContext.tsx b/src/frontend/src/contexts/alertContext.tsx
index 1741b88b6..98b2fdef8 100644
--- a/src/frontend/src/contexts/alertContext.tsx
+++ b/src/frontend/src/contexts/alertContext.tsx
@@ -26,6 +26,8 @@ const initialValue: alertContextType = {
pushNotificationList: () => {},
clearNotificationList: () => {},
removeFromNotificationList: () => {},
+ isTweakPage: false,
+ setIsTweakPage: () => {},
};
export const alertContext = createContext(initialValue);
@@ -48,6 +50,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
const [successOpen, setSuccessOpen] = useState(false);
const [notificationCenter, setNotificationCenter] = useState(false);
const [notificationList, setNotificationList] = useState([]);
+ const [isTweakPage, setIsTweakPage] = useState(false);
const pushNotificationList = (notification: AlertItemType) => {
setNotificationList((old) => {
let newNotificationList = _.cloneDeep(old);
@@ -120,6 +123,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
return (
{
- if (accessToken) {
- getLoggedUser().then((user) => {
- const isSuperUser = user.is_superuser;
- setIsAdmin(isSuperUser);
- });
- }
- }, [accessToken, isAdmin]);
function getAuthentication() {
const storedRefreshToken = cookies.get("refresh_token");
diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx
index 64142f942..b213ace9d 100644
--- a/src/frontend/src/contexts/index.tsx
+++ b/src/frontend/src/contexts/index.tsx
@@ -9,11 +9,14 @@ import { LocationProvider } from "./locationContext";
import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext";
import { UndoRedoProvider } from "./undoRedoContext";
+import { BrowserRouter } from "react-router-dom";
+import { ApiInterceptor } from "../controllers/API/api";
export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context
return (
<>
+
@@ -21,6 +24,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
+ {children}
@@ -33,6 +37,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
+
>
);
}
diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx
index 67231f4e5..2e3334f5e 100644
--- a/src/frontend/src/contexts/tabsContext.tsx
+++ b/src/frontend/src/contexts/tabsContext.tsx
@@ -30,6 +30,7 @@ import {
import { getRandomDescription, getRandomName } from "../utils/utils";
import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext";
+import { AxiosError } from "axios";
const uid = new ShortUniqueId({ length: 5 });
@@ -68,7 +69,7 @@ export const TabsContext = createContext(
);
export function TabsProvider({ children }: { children: ReactNode }) {
- const { setErrorData, setNoticeData } = useContext(alertContext);
+ const { setErrorData, setNoticeData, setSuccessData } = useContext(alertContext);
const [tabId, setTabId] = useState("");
@@ -579,6 +580,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updatedFlow = await updateFlowInDatabase(newFlow);
if (updatedFlow) {
// updates flow in state
+ setSuccessData({ title: "Changes saved successfully" });
setFlows((prevState) => {
const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
@@ -601,7 +603,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
} catch (err) {
- setErrorData(err as errorsVarType);
+ setErrorData({title: "Error while saving changes",list:[(err as AxiosError).message]});
}
}
diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx
index 52a5eea72..1ff3609e0 100644
--- a/src/frontend/src/contexts/typesContext.tsx
+++ b/src/frontend/src/contexts/typesContext.tsx
@@ -6,7 +6,7 @@ import {
useState,
} from "react";
import { Node, ReactFlowInstance } from "reactflow";
-import { getAll } from "../controllers/API";
+import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
@@ -23,6 +23,8 @@ const initialValue: typesContextType = {
setTemplates: () => {},
data: {},
setData: () => {},
+ setFetchError: () => {},
+ fetchError: false,
};
export const typesContext = createContext(initialValue);
@@ -33,14 +35,10 @@ export function TypesProvider({ children }: { children: ReactNode }) {
useState(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
+ const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
useEffect(() => {
- let delay = 1000; // Start delay of 1 second
- let intervalId: NodeJS.Timer;
- let retryCount = 0; // Count of retry attempts
- const maxRetryCount = 5; // Max retry attempts
-
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
let isMounted = true;
@@ -48,7 +46,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
try {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
- if (isMounted) {
+ if (isMounted && result?.status === 200) {
setLoading(false);
setData(result.data);
setTemplates(
@@ -78,21 +76,15 @@ export function TypesProvider({ children }: { children: ReactNode }) {
}, {})
);
}
- // Clear the interval if successful.
- clearInterval(intervalId!);
} catch (error) {
console.error("An error has occurred while fetching types.");
+ await getHealth().catch((e) => {
+ setFetchError(true);
+ });
}
}
- // Start the initial interval.
- intervalId = setInterval(getTypes, delay);
- return () => {
- // This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
- clearInterval(intervalId!);
- // Indicate that the component has been unmounted.
- isMounted = false;
- };
+ getTypes();
}, []);
function deleteNode(idx: string) {
@@ -117,6 +109,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
templates,
data,
setData,
+ fetchError,
+ setFetchError,
}}
>
{children}
diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx
index 2d05228cc..0e74670b0 100644
--- a/src/frontend/src/controllers/API/api.tsx
+++ b/src/frontend/src/controllers/API/api.tsx
@@ -24,7 +24,6 @@ function ApiInterceptor() {
async (error: AxiosError) => {
if (error.response?.status === 401) {
const refreshToken = cookies.get("refresh_token");
-
if (refreshToken) {
authenticationErrorCount = authenticationErrorCount + 1;
if (authenticationErrorCount > 3) {
@@ -103,9 +102,5 @@ function ApiInterceptor() {
return null;
}
-// Function to sleep for a given duration in milliseconds
-function sleep(ms: number) {
- return new Promise((resolve) => setTimeout(resolve, ms));
-}
export { ApiInterceptor, api };
diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts
index 796fbceb7..b49eb84ac 100644
--- a/src/frontend/src/controllers/API/index.ts
+++ b/src/frontend/src/controllers/API/index.ts
@@ -25,7 +25,7 @@ import {
* @returns {Promise>} A promise that resolves to an AxiosResponse containing all the objects.
*/
export async function getAll(): Promise> {
- return await api.get(`/api/v1/all`);
+ return await api.get(`${BASE_URL_API}all`);
}
const GITHUB_API_URL = "https://api.github.com";
@@ -47,13 +47,13 @@ export async function getRepoStars(owner: string, repo: string) {
* @returns {AxiosResponse} The API response.
*/
export async function sendAll(data: sendAllProps) {
- return await api.post(`/api/v1/predict`, data);
+ return await api.post(`${BASE_URL_API}predict`, data);
}
export async function postValidateCode(
code: string
): Promise> {
- return await api.post("/api/v1/validate/code", { code });
+ return await api.post(`${BASE_URL_API}validate/code`, { code });
}
/**
@@ -68,7 +68,7 @@ export async function postValidatePrompt(
template: string,
frontend_node: APIClassType
): Promise> {
- return await api.post("/api/v1/validate/prompt", {
+ return await api.post(`${BASE_URL_API}validate/prompt`, {
name: name,
template: template,
frontend_node: frontend_node,
@@ -112,7 +112,7 @@ export async function saveFlowToDatabase(newFlow: {
style?: FlowStyleType;
}): Promise {
try {
- const response = await api.post("/api/v1/flows/", {
+ const response = await api.post(`${BASE_URL_API}flows/`, {
name: newFlow.name,
data: newFlow.data,
description: newFlow.description,
@@ -138,7 +138,7 @@ export async function updateFlowInDatabase(
updatedFlow: FlowType
): Promise {
try {
- const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
+ const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
name: updatedFlow.name,
data: updatedFlow.data,
description: updatedFlow.description,
@@ -162,7 +162,7 @@ export async function updateFlowInDatabase(
*/
export async function readFlowsFromDatabase() {
try {
- const response = await api.get("/api/v1/flows/");
+ const response = await api.get(`${BASE_URL_API}flows/`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -175,7 +175,7 @@ export async function readFlowsFromDatabase() {
export async function downloadFlowsFromDatabase() {
try {
- const response = await api.get("/api/v1/flows/download/");
+ const response = await api.get(`${BASE_URL_API}flows/download/`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -188,7 +188,7 @@ export async function downloadFlowsFromDatabase() {
export async function uploadFlowsToDatabase(flows: FormData) {
try {
- const response = await api.post(`/api/v1/flows/upload/`, flows);
+ const response = await api.post(`${BASE_URL_API}flows/upload/`, flows);
if (response.status !== 201) {
throw new Error(`HTTP error! status: ${response.status}`);
@@ -209,7 +209,7 @@ export async function uploadFlowsToDatabase(flows: FormData) {
*/
export async function deleteFlowFromDatabase(flowId: string) {
try {
- const response = await api.delete(`/api/v1/flows/${flowId}`);
+ const response = await api.delete(`${BASE_URL_API}flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -229,7 +229,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
*/
export async function getFlowFromDatabase(flowId: number) {
try {
- const response = await api.get(`/api/v1/flows/${flowId}`);
+ const response = await api.get(`${BASE_URL_API}flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -248,7 +248,7 @@ export async function getFlowFromDatabase(flowId: number) {
*/
export async function getFlowStylesFromDatabase() {
try {
- const response = await api.get("/api/v1/flow_styles/");
+ const response = await api.get(`${BASE_URL_API}flow_styles/`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -268,7 +268,7 @@ export async function getFlowStylesFromDatabase() {
*/
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
try {
- const response = await api.post("/api/v1/flow_styles/", flowStyle, {
+ const response = await api.post(`${BASE_URL_API}flow_styles/`, flowStyle, {
headers: {
accept: "application/json",
"Content-Type": "application/json",
@@ -291,7 +291,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
* @returns {Promise>} A promise that resolves to an AxiosResponse containing the version information.
*/
export async function getVersion() {
- const respnose = await api.get("/api/v1/version");
+ const respnose = await api.get(`${BASE_URL_API}version`);
return respnose.data;
}
@@ -313,7 +313,7 @@ export async function getHealth() {
export async function getBuildStatus(
flowId: string
): Promise {
- return await api.get(`/api/v1/build/${flowId}/status`);
+ return await api.get(`${BASE_URL_API}build/${flowId}/status`);
}
//docs for postbuildinit
@@ -326,7 +326,7 @@ export async function getBuildStatus(
export async function postBuildInit(
flow: FlowType
): Promise> {
- return await api.post(`/api/v1/build/init/${flow.id}`, flow);
+ return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
}
// fetch(`/upload/${id}`, {
@@ -344,14 +344,14 @@ export async function uploadFile(
): Promise> {
const formData = new FormData();
formData.append("file", file);
- return await api.post(`/api/v1/upload/${id}`, formData);
+ return await api.post(`${BASE_URL_API}upload/${id}`, formData);
}
export async function postCustomComponent(
code: string,
apiClass: APIClassType
): Promise> {
- return await api.post(`/api/v1/custom_component`, { code });
+ return await api.post(`${BASE_URL_API}custom_component`, { code });
}
export async function onLogin(user: LoginType) {
diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx
index 2542f4903..3a7cbd9f5 100644
--- a/src/frontend/src/index.tsx
+++ b/src/frontend/src/index.tsx
@@ -17,10 +17,7 @@ const root = ReactDOM.createRoot(
);
root.render(
-
-
-
);
reportWebVitals();
diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx
index 238641279..5c17e42a9 100644
--- a/src/frontend/src/modals/codeAreaModal/index.tsx
+++ b/src/frontend/src/modals/codeAreaModal/index.tsx
@@ -28,7 +28,8 @@ export default function CodeAreaModal({
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const [height, setHeight] = useState(null);
- const { setErrorData, setSuccessData } = useContext(alertContext);
+ const { setErrorData, setSuccessData, isTweakPage } =
+ useContext(alertContext);
const [error, setError] = useState<{
detail: { error: string | undefined; traceback: string | undefined };
} | null>(null);
@@ -39,7 +40,7 @@ export default function CodeAreaModal({
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
return;
}
- processCode();
+ if (!isTweakPage) processCode();
}, []);
function processNonDynamicField() {
diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx
index 075f0a651..7729f78fe 100644
--- a/src/frontend/src/modals/flowSettingsModal/index.tsx
+++ b/src/frontend/src/modals/flowSettingsModal/index.tsx
@@ -12,15 +12,14 @@ export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
- const { setSuccessData } = useContext(alertContext);
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
- setName(flow.name);
- setDescription(flow.description);
- }, [flow.name, flow.description]);
- const [name, setName] = useState(flow.name);
- const [description, setDescription] = useState(flow.description);
+ setName(flow!.name);
+ setDescription(flow!.description);
+ }, [flow!.name, flow!.description]);
+ const [name, setName] = useState(flow!.name);
+ const [description, setDescription] = useState(flow!.description);
const [invalidName, setInvalidName] = useState(false);
function handleClick(): void {
@@ -28,7 +27,6 @@ export default function FlowSettingsModal({
savedFlow!.name = name;
savedFlow!.description = description;
saveFlow(savedFlow!);
- setSuccessData({ title: "Changes saved successfully" });
setOpen(false);
}
return (
diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx
index 64b766416..e52cb863d 100644
--- a/src/frontend/src/pages/AdminPage/index.tsx
+++ b/src/frontend/src/pages/AdminPage/index.tsx
@@ -133,8 +133,6 @@ export default function AdminPage() {
updateUser(userId, userEdit)
.then((res) => {
- console.log(res);
-
resetFilter();
setSuccessData({
title: "Success! User edited!",
@@ -196,7 +194,7 @@ export default function AdminPage() {
Welcome back!
- Here's a list of all users!
+ Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.