Giter Club home page Giter Club logo

Comments (5)

dosubot avatar dosubot commented on July 30, 2024 2

To fix the KeyError when uploading a file to a folder due to the recent commit that made each flow have a default folder name, you need to ensure that the folder_name and folder_description keys are present in the uploaded data. Additionally, you can use the utility function create_default_folder_if_it_doesnt_exist to handle the creation of a default folder if it doesn't exist.

Here is the relevant part of the code that handles the upload:

@router.post("/upload/", response_model=List[FlowRead], status_code=201)
async def upload_file(
    *,
    session: Session = Depends(get_session),
    file: UploadFile = File(...),
    current_user: User = Depends(get_current_active_user),
):
    """Upload flows from a file."""
    contents = await file.read()
    data = orjson.loads(contents)

    if not data:
        raise HTTPException(status_code=400, detail="No flows found in the file")

    if "folder_name" not in data or "folder_description" not in data:
        raise HTTPException(status_code=400, detail="Missing folder_name or folder_description in the data")

    folder_results = session.exec(
        select(Folder).where(
            Folder.name == data["folder_name"],
            Folder.user_id == current_user.id,
        )
    )
    existing_folder_names = [folder.name for folder in folder_results]

    if existing_folder_names:
        data["folder_name"] = f"{data['folder_name']} ({len(existing_folder_names) + 1})"

    folder = FolderCreate(name=data["folder_name"], description=data["folder_description"])

    new_folder = Folder.model_validate(folder, from_attributes=True)
    new_folder.id = None
    new_folder.user_id = current_user.id
    session.add(new_folder)
    session.commit()
    session.refresh(new_folder)

    del data["folder_name"]
    del data["folder_description"]

    if "flows" in data:
        flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data["flows"]])
    else:
        raise HTTPException(status_code=400, detail="No flows found in the data")
    # Now we set the user_id for all flows
    for flow in flow_list.flows:
        flow.user_id = current_user.id
        flow.folder_id = new_folder.id

    return create_flows(session=session, flow_list=flow_list, current_user=current_user)

Additionally, you can use the create_default_folder_if_it_doesnt_exist function to ensure a default folder is created if it doesn't exist:

def create_default_folder_if_it_doesnt_exist(session: Session, user_id: UUID):
    folder = session.exec(select(Folder).where(Folder.user_id == user_id)).first()
    if not folder:
        folder = Folder(name=DEFAULT_FOLDER_NAME, user_id=user_id, description=DEFAULT_FOLDER_DESCRIPTION)
        session.add(folder)
        session.commit()
        session.refresh(folder)
        session.exec(
            update(Flow)  # type: ignore
            .where(
                and_(
                    Flow.folder_id == None,  # type: ignore # noqa
                    Flow.user_id == user_id,
                )
            )
            .values(folder_id=folder.id)
        )
        session.commit()
    return folder

This ensures that the necessary keys are present and prevents the KeyError, while also handling the creation of a default folder if needed [1][2].

Additionally, you can refer to the uploadFlowsFromFolders function in the frontend service to ensure the correct API endpoint and data format are used when uploading flows:

export async function uploadFlowsFromFolders(
  flows: FormData
): Promise<FlowType[]> {
  try {
    const response = await api.post(`${BASE_URL_API}folders/upload/`, flows);

    if (response?.status !== 201) {
      throw new Error(`HTTP error! status: ${response?.status}`);
    }
    return response.data;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

This function handles the API call to upload flows to the server, ensuring that the data is correctly formatted and the appropriate endpoint is used [3].

Furthermore, the useFolderStore in the frontend ensures that folders are managed correctly, including handling the default folder and starter projects:

import { create } from "zustand";
import { DEFAULT_FOLDER, STARTER_FOLDER_NAME } from "../constants/constants";
import {
  getFolderById,
  getFolders,
  uploadFlowsFromFolders,
} from "../pages/MainPage/services";
import { FoldersStoreType } from "../types/zustand/folders";
import useFlowsManagerStore from "./flowsManagerStore";

export const useFolderStore = create<FoldersStoreType>((set, get) => ({
  folders: [],
  getFoldersApi: (refetch = false) => {
    return new Promise<void>((resolve, reject) => {
      if (get()?.folders.length === 0 || refetch === true) {
        get().setLoading(true);
        getFolders().then(
          (res) => {
            const foldersWithoutStarterProjects = res.filter(
              (folder) => folder.name !== STARTER_FOLDER_NAME,
            );

            const starterProjects = res.find(
              (folder) => folder.name === STARTER_FOLDER_NAME,
            );

            set({ starterProjectId: starterProjects!.id ?? "" });
            set({ folders: foldersWithoutStarterProjects });

            const myCollectionId = res?.find(
              (f) => f.name === DEFAULT_FOLDER,
            )?.id;

            set({ myCollectionId });

            if (refetch === true) {
              useFlowsManagerStore.getState().refreshFlows();
              useFlowsManagerStore.getState().setAllFlows;
            }

            get().setLoading(false);
            resolve();
          },
          () => {
            set({ folders: [] });
            get().setLoading(false);
            reject();
          },
        );
      }
    });
  },
  setFolders: (folders) => set(() => ({ folders: folders })),
  loading: false,
  setLoading: (loading) => set(() => ({ loading: loading })),
  getFolderById: (id) => {
    get().setLoadingById(true);
    if (id) {
      getFolderById(id).then(
        (res) => {
          const setAllFlows = useFlowsManagerStore.getState().setAllFlows;
          setAllFlows(res.flows);
          set({ selectedFolder: res });
          get().setLoadingById(false);
        },
        () => {
          get().setLoadingById(false);
        },
      );
    }
  },
  selectedFolder: null,
  loadingById: false,
  setLoadingById: (loading) => set(() => ({ loadingById: loading })),
  getMyCollectionFolder: () => {
    const folders = get().folders;
    const myCollectionId = folders?.find((f) => f.name === DEFAULT_FOLDER)?.id;
    if (myCollectionId) {
      getFolderById(myCollectionId).then((res) => {
        set({ myCollectionFlows: res });
      });
    }
  },
  setMyCollectionFlow: (folder) => set(() => ({ myCollectionFlows: folder })),
  myCollectionFlows: null,
  setMyCollectionId: () => {
    const folders = get().folders;
    const myCollectionId = folders?.find((f) => f.name === DEFAULT_FOLDER)?.id;
    if (myCollectionId) {
      set({ myCollectionId });
    }
  },
  myCollectionId: "",
  folderToEdit: null,
  setFolderToEdit: (folder) => set(() => ({ folderToEdit: folder })),
  folderUrl: "",
  setFolderUrl: (url) => set(() => ({ folderUrl: url })),
  folderDragging: false,
  setFolderDragging: (folder) => set(() => ({ folderDragging: folder })),
  folderIdDragging: "",
  setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })),
  uploadFolder: () => {
    return new Promise<void>(() => {
      const input = document.createElement("input");
      input.type = "file";
      input.onchange = (event: Event) => {
        if (
          (event.target as HTMLInputElement).files![0].type ===
          "application/json"
        ) {
          const file = (event.target as HTMLInputElement).files![0];
          const formData = new FormData();
          formData.append("file", file);
          uploadFlowsFromFolders(formData).then(() => {
            get().getFoldersApi(true);
          });
        }
      };
      input.click();
    });
  },
  starterProjectId: "",
  setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
}));

This ensures that the frontend correctly handles folder management, including the creation and uploading of flows to the appropriate folders [4].

Details

About Dosu
This response is meant to be useful and save you time. It is not meant to be a precise solution, but rather a starting point for your own research.

Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant.

To continue the conversation, mention @dosu.

from langflow.

dosubot avatar dosubot commented on July 30, 2024 2

To address your concerns and provide a solution for directly uploading a Flow, you can implement a direct "upload flow" option that allows for the Name and Description of the Flow to be updated accordingly.

Backend: Add an Endpoint for Uploading a Single Flow

You can add a new endpoint to handle the upload of a single flow. This endpoint will ensure that the Name and Description are included in the uploaded data:

@router.post("/upload_flow/", response_model=FlowRead, status_code=201)
async def upload_single_flow(
    *,
    session: Session = Depends(get_session),
    file: UploadFile = File(...),
    current_user: User = Depends(get_current_active_user),
):
    """Upload a single flow from a file."""
    contents = await file.read()
    data = orjson.loads(contents)

    if not data:
        raise HTTPException(status_code=400, detail="No flow found in the file")

    if "name" not in data or "description" not in data:
        raise HTTPException(status_code=400, detail="Missing name or description in the data")

    flow = FlowCreate(name=data["name"], description=data["description"], user_id=current_user.id)
    new_flow = Flow.model_validate(flow, from_attributes=True)
    new_flow.id = None
    session.add(new_flow)
    session.commit()
    session.refresh(new_flow)

    return new_flow

Frontend: Add a Function to Handle the Upload of a Single Flow

Next, you can add a function in the frontend to handle the API call for uploading a single flow:

export async function uploadSingleFlow(flow: FormData): Promise<FlowType> {
  try {
    const response = await api.post(`${BASE_URL_API}upload_flow/`, flow);

    if (response?.status !== 201) {
      throw new Error(`HTTP error! status: ${response?.status}`);
    }
    return response.data;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

Frontend: Add a Button and Handler for Uploading a Single Flow

Finally, you can add a button and handler in your frontend to allow users to upload a single flow directly:

import React from 'react';
import { uploadSingleFlow } from '../services';

const UploadFlowButton = () => {
  const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file && file.type === "application/json") {
      const formData = new FormData();
      formData.append("file", file);
      try {
        const uploadedFlow = await uploadSingleFlow(formData);
        console.log("Uploaded Flow:", uploadedFlow);
      } catch (error) {
        console.error("Error uploading flow:", error);
      }
    }
  };

  return (
    <input type="file" accept="application/json" onChange={handleUpload} />
  );
};

export default UploadFlowButton;

This setup allows you to upload a single flow directly, ensuring that the Name and Description are updated as specified in the uploaded JSON file. This should streamline the process and avoid the need to create a blank project first.

To continue talking to Dosu, mention @dosu.

from langflow.

jordanrfrazier avatar jordanrfrazier commented on July 30, 2024

I see. The upload button on the main page is specifically for uploading folders, not flows.

Perhaps just a quick json validation check is enough to avoid confusion here

from langflow.

jordanrfrazier avatar jordanrfrazier commented on July 30, 2024

Is there a way to upload a Flow directly? I've been creating a blank project, then importing, but that doesn't update the Name or Description of the Flow. Can we have a direct "upload flow" option?

from langflow.

nicoloboschi avatar nicoloboschi commented on July 30, 2024

@jordanrfrazier to import a specific flow you need to create a new blank one, from the dropdown click "Import" and pass it the JSON file.

The "upload" button in the main page is for folders (I've sent this improvement #2125)

from langflow.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.