Merge branch 'cz/mergeAll' into fix/minor_ui_adjustments
This commit is contained in:
commit
79615816a4
4 changed files with 91 additions and 38 deletions
|
|
@ -31,25 +31,60 @@ def create_flow(
|
||||||
flow: FlowCreate,
|
flow: FlowCreate,
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Create a new flow."""
|
try:
|
||||||
if flow.user_id is None:
|
"""Create a new flow."""
|
||||||
flow.user_id = current_user.id
|
if flow.user_id is None:
|
||||||
|
flow.user_id = current_user.id
|
||||||
|
|
||||||
db_flow = Flow.model_validate(flow, from_attributes=True)
|
# First check if the flow.name is unique
|
||||||
db_flow.updated_at = datetime.now(timezone.utc)
|
# there might be flows with name like: "MyFlow", "MyFlow (1)", "MyFlow (2)"
|
||||||
|
# so we need to check if the name is unique with `like` operator
|
||||||
|
# if we find a flow with the same name, we add a number to the end of the name
|
||||||
|
# based on the highest number found
|
||||||
|
if session.exec(select(Flow).where(Flow.name == flow.name).where(Flow.user_id == current_user.id)).first():
|
||||||
|
flows = session.exec(
|
||||||
|
select(Flow).where(Flow.name.like(f"{flow.name} (%")).where(Flow.user_id == current_user.id)
|
||||||
|
).all()
|
||||||
|
if flows:
|
||||||
|
numbers = [int(flow.name.split("(")[1].split(")")[0]) for flow in flows]
|
||||||
|
flow.name = f"{flow.name} ({max(numbers) + 1})"
|
||||||
|
else:
|
||||||
|
flow.name = f"{flow.name} (1)"
|
||||||
|
|
||||||
if db_flow.folder_id is None:
|
db_flow = Flow.model_validate(flow, from_attributes=True)
|
||||||
# Make sure flows always have a folder
|
db_flow.updated_at = datetime.now(timezone.utc)
|
||||||
default_folder = session.exec(
|
|
||||||
select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME, Folder.user_id == current_user.id)
|
|
||||||
).first()
|
|
||||||
if default_folder:
|
|
||||||
db_flow.folder_id = default_folder.id
|
|
||||||
|
|
||||||
session.add(db_flow)
|
if db_flow.folder_id is None:
|
||||||
session.commit()
|
# Make sure flows always have a folder
|
||||||
session.refresh(db_flow)
|
default_folder = session.exec(
|
||||||
return db_flow
|
select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME, Folder.user_id == current_user.id)
|
||||||
|
).first()
|
||||||
|
if default_folder:
|
||||||
|
db_flow.folder_id = default_folder.id
|
||||||
|
|
||||||
|
session.add(db_flow)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_flow)
|
||||||
|
return db_flow
|
||||||
|
except Exception as e:
|
||||||
|
# If it is a validation error, return the error message
|
||||||
|
if hasattr(e, "errors"):
|
||||||
|
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||||
|
elif "UNIQUE constraint failed" in str(e):
|
||||||
|
# Get the name of the column that failed
|
||||||
|
columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
|
||||||
|
# UNIQUE constraint failed: flow.user_id, flow.name
|
||||||
|
# or UNIQUE constraint failed: flow.name
|
||||||
|
# if the column has id in it, we want the other column
|
||||||
|
column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
|
||||||
|
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
|
||||||
|
) from e
|
||||||
|
elif isinstance(e, HTTPException):
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[FlowRead], status_code=200)
|
@router.get("/", response_model=list[FlowRead], status_code=200)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from langflow.helpers.flow import generate_unique_flow_name
|
|
||||||
from langflow.helpers.folders import generate_unique_folder_name
|
|
||||||
import orjson
|
import orjson
|
||||||
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
|
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
|
||||||
from sqlalchemy import or_, update
|
from sqlalchemy import or_, update
|
||||||
|
|
@ -9,6 +7,8 @@ from sqlmodel import Session, select
|
||||||
|
|
||||||
from langflow.api.v1.flows import create_flows
|
from langflow.api.v1.flows import create_flows
|
||||||
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
|
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
|
||||||
|
from langflow.helpers.flow import generate_unique_flow_name
|
||||||
|
from langflow.helpers.folders import generate_unique_folder_name
|
||||||
from langflow.services.auth.utils import get_current_active_user
|
from langflow.services.auth.utils import get_current_active_user
|
||||||
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
|
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
|
||||||
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
|
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
|
||||||
|
|
@ -35,17 +35,27 @@ def create_folder(
|
||||||
try:
|
try:
|
||||||
new_folder = Folder.model_validate(folder, from_attributes=True)
|
new_folder = Folder.model_validate(folder, from_attributes=True)
|
||||||
new_folder.user_id = current_user.id
|
new_folder.user_id = current_user.id
|
||||||
|
# First check if the folder.name is unique
|
||||||
folder_results = session.exec(
|
# there might be flows with name like: "MyFlow", "MyFlow (1)", "MyFlow (2)"
|
||||||
select(Folder).where(
|
# so we need to check if the name is unique with `like` operator
|
||||||
Folder.name.like(f"{new_folder.name}%"), # type: ignore
|
# if we find a flow with the same name, we add a number to the end of the name
|
||||||
Folder.user_id == current_user.id,
|
# based on the highest number found
|
||||||
|
if session.exec(
|
||||||
|
statement=select(Folder).where(Folder.name == new_folder.name).where(Folder.user_id == current_user.id)
|
||||||
|
).first():
|
||||||
|
folder_results = session.exec(
|
||||||
|
select(Folder).where(
|
||||||
|
Folder.name.like(f"{new_folder.name}%"), # type: ignore
|
||||||
|
Folder.user_id == current_user.id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
if folder_results:
|
||||||
existing_folder_names = [folder.name for folder in folder_results]
|
folder_names = [folder.name for folder in folder_results]
|
||||||
|
folder_numbers = [int(name.split("(")[-1].split(")")[0]) for name in folder_names if "(" in name]
|
||||||
if existing_folder_names:
|
if folder_numbers:
|
||||||
new_folder.name = f"{new_folder.name} ({len(existing_folder_names) + 1})"
|
new_folder.name = f"{new_folder.name} ({max(folder_numbers) + 1})"
|
||||||
|
else:
|
||||||
|
new_folder.name = f"{new_folder.name} (1)"
|
||||||
|
|
||||||
session.add(new_folder)
|
session.add(new_folder)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleUndo(e: KeyboardEvent) {
|
function handleUndo(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (!isWrappedWithClass(e, "noundo")) {
|
if (!isWrappedWithClass(e, "noundo")) {
|
||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleRedo(e: KeyboardEvent) {
|
function handleRedo(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (!isWrappedWithClass(e, "noundo")) {
|
if (!isWrappedWithClass(e, "noundo")) {
|
||||||
redo();
|
redo();
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +196,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleGroup(e: KeyboardEvent) {
|
function handleGroup(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (selectionMenuVisible) {
|
if (selectionMenuVisible) {
|
||||||
handleGroupNode();
|
handleGroupNode();
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ export default function Page({
|
||||||
function handleDuplicate(e: KeyboardEvent) {
|
function handleDuplicate(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
const selectedNode = nodes.filter((obj) => obj.selected);
|
const selectedNode = nodes.filter((obj) => obj.selected);
|
||||||
if (selectedNode.length > 0) {
|
if (selectedNode.length > 0) {
|
||||||
paste(
|
paste(
|
||||||
|
|
@ -220,7 +220,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleCopy(e: KeyboardEvent) {
|
function handleCopy(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (
|
if (
|
||||||
!isWrappedWithClass(e, "nocopy") &&
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
window.getSelection()?.toString().length === 0 &&
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
|
@ -232,7 +232,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleCut(e: KeyboardEvent) {
|
function handleCut(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (
|
if (
|
||||||
!isWrappedWithClass(e, "nocopy") &&
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
window.getSelection()?.toString().length === 0 &&
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
|
@ -244,7 +244,7 @@ export default function Page({
|
||||||
|
|
||||||
function handlePaste(e: KeyboardEvent) {
|
function handlePaste(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (
|
if (
|
||||||
!isWrappedWithClass(e, "nocopy") &&
|
!isWrappedWithClass(e, "nocopy") &&
|
||||||
window.getSelection()?.toString().length === 0 &&
|
window.getSelection()?.toString().length === 0 &&
|
||||||
|
|
@ -260,7 +260,7 @@ export default function Page({
|
||||||
|
|
||||||
function handleDelete(e: KeyboardEvent) {
|
function handleDelete(e: KeyboardEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
(e as unknown as Event).stopImmediatePropagation();
|
||||||
if (!isWrappedWithClass(e, "nodelete") && lastSelection) {
|
if (!isWrappedWithClass(e, "nodelete") && lastSelection) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,7 @@ export default function NodeToolbarComponent({
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const hasCode = Object.keys(data.node!.template).includes("code");
|
const hasCode = Object.keys(data.node!.template).includes("code");
|
||||||
|
const [deleteIsFocus, setDeleteIsFocus] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -697,14 +698,21 @@ export default function NodeToolbarComponent({
|
||||||
dataTestId="download-button-modal"
|
dataTestId="download-button-modal"
|
||||||
/>
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
|
<SelectItem
|
||||||
|
value={"delete"}
|
||||||
|
className="focus:bg-red-400/[.20]"
|
||||||
|
onFocus={() => setDeleteIsFocus(true)}
|
||||||
|
onBlur={() => setDeleteIsFocus(false)}
|
||||||
|
>
|
||||||
<div className="font-red flex text-status-red">
|
<div className="font-red flex text-status-red">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Trash2"
|
name="Trash2"
|
||||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="">Delete</span>{" "}
|
<span className="">Delete</span>{" "}
|
||||||
<span className="justify absolute right-2 top-2 flex items-center rounded-sm bg-muted px-1 py-[0.2]">
|
<span
|
||||||
|
className={`justify absolute right-2 top-2 flex items-center rounded-sm px-1 py-[0.2] ${deleteIsFocus ? " " : "bg-muted"}`}
|
||||||
|
>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Delete"
|
name="Delete"
|
||||||
className="h-4 w-4 stroke-2 text-red-400"
|
className="h-4 w-4 stroke-2 text-red-400"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue