Merge branch 'shortcuts_settings' of github.com:langflow-ai/langflow into shortcuts_settings

This commit is contained in:
igorrCarvalho 2024-06-09 13:24:00 -03:00
commit 93ba523dee
134 changed files with 693 additions and 403 deletions

View file

@ -174,3 +174,12 @@ body {
border: none !important;
outline: none;
}
/* selected */
.react-flow__edge.selected .react-flow__edge-path {
stroke: var(--selected) !important;
}
.react-flow__edge .react-flow__edge-path {
stroke: var(--connection) !important;
}

View file

@ -4,7 +4,7 @@ import SwitchOutputView from "./components/switchOutputView";
export default function OutputModal({ open, setOpen, nodeId }): JSX.Element {
return (
<BaseModal open={open} setOpen={setOpen} size="medium">
<BaseModal open={open} setOpen={setOpen} size="medium-tall">
<BaseModal.Header description="Inspect the output of the component below.">
<div className="flex items-center">
<span className="pr-2">Component Output</span>

View file

@ -274,7 +274,9 @@ export default function ParameterComponent({
: "Please build the component first"
}
>
<button
<Button
variant="none"
size="none"
disabled={!displayOutputPreview || unknownOutput}
onClick={() => setOpenOutputModal(true)}
data-testid={`output-inspection-${title.toLowerCase()}`}
@ -288,7 +290,7 @@ export default function ParameterComponent({
)}
name={"ScanEye"}
/>
</button>
</Button>
</ShadTooltip>
)}
</div>

View file

@ -10,6 +10,7 @@ import {
RUN_TIMESTAMP_PREFIX,
STATUS_BUILD,
STATUS_BUILDING,
TOOLTIP_OUTDATED_NODE,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { countHandlesFn } from "../helpers/count-handles";
@ -35,6 +36,8 @@ import getFieldTitle from "../utils/get-field-title";
import sortFields from "../utils/sort-fields";
import isWrappedWithClass from "../../pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class";
import ParameterComponent from "./components/parameterComponent";
import { postCustomComponent } from "../../controllers/API";
import { cloneDeep } from "lodash";
export default function GenericNode({
data,
@ -204,6 +207,33 @@ export default function GenericNode({
setShowNode(data.showNode ?? true);
}, [data.showNode]);
const [loadingUpdate, setLoadingUpdate] = useState(false);
const handleUpdateCode = () => {
setLoadingUpdate(true);
takeSnapshot();
// to update we must get the code from the templates in useTypesStore
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code.value;
if (data.node) {
postCustomComponent(currentCode, data.node)
.then((apiReturn) => {
const { data } = apiReturn;
if (data && updateNodeCode) {
updateNodeCode(data, currentCode, "code");
setLoadingUpdate(false);
}
})
.catch((err) => {
console.log(err);
});
}
};
const memoizedNodeToolbarComponent = useMemo(() => {
return (
<NodeToolbar>
@ -226,8 +256,6 @@ export default function GenericNode({
showNode={showNode}
openAdvancedModal={false}
onCloseAdvancedModal={() => {}}
updateNodeCode={updateNodeCode}
isOutdated={isOutdated}
selected={selected}
/>
</NodeToolbar>
@ -448,54 +476,71 @@ export default function GenericNode({
</div>
{showNode && (
<>
<ShadTooltip
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
<div className="flex flex-shrink-0 items-center gap-1">
{isOutdated && (
<ShadTooltip content={TOOLTIP_OUTDATED_NODE}>
<Button
onClick={handleUpdateCode}
variant="secondary"
className={"h-9 px-1.5"}
loading={loadingUpdate}
>
<IconComponent
name="AlertTriangle"
className="h-5 w-5 text-status-yellow"
/>
</Button>
</ShadTooltip>
)}
<ShadTooltip
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
</div>
</div>
)}
</div>
<div className="justify-left flex font-normal text-muted-foreground">
<div>Duration:</div>
<div className="ml-1 text-status-blue">
{validationStatus?.data.duration}
</div>
)}
</div>
<div className="justify-left flex font-normal text-muted-foreground">
<div>Duration:</div>
<div className="ml-1 text-status-blue">
{validationStatus?.data.duration}
</div>
</div>
</div>
)
}
side="bottom"
>
<Button
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
variant="secondary"
className={"group h-9 px-1.5"}
)
}
side="bottom"
>
<div
data-testid={
`button_run_` + data?.node?.display_name.toLowerCase()
}
<Button
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
variant="secondary"
className={"group h-9 px-1.5"}
>
{renderIconStatus()}
</div>
</Button>
</ShadTooltip>
<div
data-testid={
`button_run_` + data?.node?.display_name.toLowerCase()
}
>
{renderIconStatus()}
</div>
</Button>
</ShadTooltip>
</div>
</>
)}
</div>

View file

@ -23,10 +23,11 @@ const useCheckCodeValidity = (
if (!thisNodeTemplate.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const componentsToIgnore = ["Custom Component", "Prompt"];
const componentsToIgnore = ["CustomComponent", "Prompt"];
if (
currentCode !== thisNodesCode &&
!componentsToIgnore.includes(data.node!.display_name)
!componentsToIgnore.includes(data.type) &&
!(data.node?.edited ?? false)
) {
setIsOutdated(true);
} else {

View file

@ -19,6 +19,7 @@ const useUpdateNodeCode = (
node: newNodeClass,
description: newNodeClass.description ?? dataNode.description,
display_name: newNodeClass.display_name ?? dataNode.display_name,
edited: false,
};
newNode.data.node.template[name].value = code;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,10 +1,12 @@
import TableAutoCellRender from "../tableComponent/components/tableAutoCellRender";
export default function ArrayReader({ array }: { array: any[] }): JSX.Element {
//TODO check array type
return (
<div>
<ul>
{array.map((item, index) => (
<li key={index}>{item}</li>
<li key={index}>{<TableAutoCellRender value={item} />}</li>
))}
</ul>
</div>

View file

@ -217,7 +217,7 @@ export default function CollectionCardComponent({
data-testid={`card-${convertTestName(data.name)}`}
//TODO check color schema
className={cn(
"group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
"group relative flex h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
disabled ? "pointer-events-none opacity-50" : "",
onClick ? "cursor-pointer" : "",
isSelectedCard ? "border border-selected" : "",

View file

@ -1,21 +0,0 @@
import { gradients } from "../../utils/styleUtils";
export default function GradientChooserComponent({ value, onChange }) {
return (
<div className="flex flex-wrap items-center justify-start gap-2">
{gradients.map((gradient, idx) => (
<div
onClick={() => {
onChange(gradient);
}}
className={
"duration-400 h-12 w-12 cursor-pointer rounded-full transition-all " +
gradient +
(value === gradient ? " shadow-lg ring-2 ring-primary" : "")
}
key={idx}
></div>
))}
</div>
);
}

View file

@ -9,7 +9,7 @@ import {
import { useNavigate } from "react-router-dom";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import { SAVED_HOVER } from "../../../../constants/constants";
import { IS_MAC, SAVED_HOVER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal";
import FlowLogsModal from "../../../../modals/flowLogsModal";
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
@ -115,7 +115,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
title: UPLOAD_ERROR_ALERT,
list: [error],
});
},
}
);
}}
>
@ -139,7 +139,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
>
<IconComponent name="Undo" className="header-menu-options " />
Undo
{navigator.userAgent.toUpperCase().includes("MAC") ? (
{IS_MAC ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
@ -161,7 +161,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
>
<IconComponent name="Redo" className="header-menu-options " />
Redo
{navigator.userAgent.toUpperCase().includes("MAC") ? (
{IS_MAC ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
@ -201,7 +201,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn(
"h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
)}
/>
{printByBuildStatus()}

View file

@ -1,9 +1,12 @@
import { useContext } from "react";
import profileCircle from "../../assets/profile-circle.png";
import { FaDiscord, FaGithub } from "react-icons/fa";
import { RiTwitterXFill } from "react-icons/ri";
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown";
import {
BACKEND_URL,
BASE_URL_API,
LOCATIONS_TO_RETURN,
USER_PROJECTS_HEADER,
} from "../../constants/constants";
@ -192,34 +195,35 @@ export default function Header(): JSX.Element {
variant="none"
size="none"
data-testid="user-profile-settings"
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: "bg-gray-500"))
}
/>
>
<img
src={
`${BACKEND_URL.slice(
0,
BACKEND_URL.length - 1,
)}${BASE_URL_API}files/profile_pictures/${userData?.profile_image ?? "Space/046-rocket.png"}` ??
profileCircle
}
className="h-7 w-7 focus-visible:outline-0 "
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{!autoLogin && (
<>
<DropdownMenuLabel>
<div className="flex items-center gap-3">
<div
className={
"h-5 w-5 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: "bg-gray-500"))
<img
src={
`${BACKEND_URL.slice(
0,
BACKEND_URL.length - 1,
)}${BASE_URL_API}files/profile_pictures/${userData?.profile_image}` ??
profileCircle
}
className="h-5 w-5 focus-visible:outline-0 "
/>
{userData?.username ?? "User"}
</div>
</DropdownMenuLabel>

View file

@ -13,7 +13,9 @@ function RecordsOutputComponent({
rows: any;
columnMode?: "intersection" | "union";
}) {
console.log("rows", rows);
const columns = extractColumnsFromRows(rows, columnMode);
console.log("columns", columns);
const columnDefs = columns.map((col, idx) => ({
...col,

View file

@ -17,7 +17,7 @@ export default function TableOptions({
stateChange: boolean;
}): JSX.Element {
return (
<div className={cn("absolute bottom-4 left-6")}>
<div className={cn("absolute bottom-3 left-6")}>
<div className="flex items-center gap-2">
<div>
<ShadTooltip content="Reset Columns">

View file

@ -9,20 +9,17 @@ import { Badge } from "../../../ui/badge";
export default function TableAutoCellRender({
value,
}: CustomCellRendererProps) {
}: CustomCellRendererProps | { value: any }) {
function getCellType() {
switch (typeof value) {
case "object":
if (value === null) {
return String(value);
} else if (Array.isArray(value)) {
return <ArrayReader array={value} />;
} else if (value.definitions) {
// use a custom render defined by the sender
return <ObjectRender object={value} />;
} else {
return <ObjectRender object={value} />;
}
break;
case "string":
if (isTimeStampString(value)) {
return <DateReader date={value} />;

View file

@ -12,8 +12,8 @@ import { cn, toTitleCase } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import TableOptions from "./components/TableOptions";
import resetGrid from "./utils/reset-grid-columns";
import { useParams } from "react-router-dom";
import resetGrid from "./utils/reset-grid-columns";
interface TableComponentProps extends AgGridReactProps {
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
@ -128,7 +128,8 @@ const TableComponent = forwardRef<
>
<AgGridReact
{...props}
className={cn(props.className, "custom-scroll")}
animateRows={false}
className={cn(props.className, "cusm-scroll")}
defaultColDef={{
minWidth: 100,
autoHeight: true,
@ -139,20 +140,19 @@ const TableComponent = forwardRef<
onGridReady={onGridReady}
onColumnMoved={onColumnMoved}
onStateUpdated={(e) => {
if (
e.sources.includes("columnVisibility") ||
e.sources.includes("columnOrder")
) {
console.log(e);
if (e.sources.some((source) => source.includes("column"))) {
setColumnStateChange(true);
}
}}
/>
<TableOptions
stateChange={columnStateChange}
hasSelection={realRef.current?.api.getSelectedRows().length > 0}
hasSelection={realRef.current?.api?.getSelectedRows().length > 0}
duplicateRow={props.onDuplicate ? props.onDuplicate : undefined}
deleteRow={props.onDelete ? props.onDelete : undefined}
resetGrid={() => {
console.log("teste");
resetGrid(realRef, initialColumnDefs);
setTimeout(() => {
setColumnStateChange(false);

View file

@ -5,7 +5,7 @@ import { cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {

View file

@ -31,6 +31,8 @@ export const INVALID_SELECTION_ERROR_ALERT = "Invalid selection";
export const EDIT_PASSWORD_ERROR_ALERT = "Error changing password";
export const EDIT_PASSWORD_ALERT_LIST = "Passwords do not match";
export const SAVE_ERROR_ALERT = "Error saving changes";
export const PROFILE_PICTURES_GET_ERROR_ALERT =
"Error retrieving profile pictures";
export const SIGNUP_ERROR_ALERT = "Error signing up";
export const APIKEY_ERROR_ALERT = "API Key Error";
export const NOAPI_ERROR_ALERT =

View file

@ -589,7 +589,7 @@ export const CONTROL_INPUT_STATE = {
export const CONTROL_PATCH_USER_STATE = {
password: "",
cnfPassword: "",
gradient: "",
profilePicture: "",
apikey: "",
};
@ -661,6 +661,9 @@ export const OUTPUT_TYPES = new Set([
export const CHAT_FIRST_INITIAL_TEXT =
"Start a conversation and click the agent's thoughts";
export const TOOLTIP_OUTDATED_NODE =
"Your component is outdated. Click to update (data may be lost)";
export const CHAT_SECOND_INITIAL_TEXT = "to inspect the chaining process.";
export const ZERO_NOTIFICATIONS = "No new notifications";
@ -731,14 +734,16 @@ export const AUTHORIZED_DUPLICATE_REQUESTS = [
export const SAVE_DEBOUNCE_TIME = 300;
export const IS_MAC = navigator.userAgent.toUpperCase().includes("MAC");
export const defaultShortcuts = [
{
name: "Advanced Settings",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Shift + A`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + A`,
},
{
name: "Minimize",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Q`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Q`,
},
{
name: "Code",
@ -746,23 +751,23 @@ export const defaultShortcuts = [
},
{
name: "Copy",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + C`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + C`,
},
{
name: "Duplicate",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + D`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + D`,
},
{
name: "Share",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Shift + S`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + S`,
},
{
name: "Docs",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Shift + D`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
},
{
name: "Save",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + S`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
},
{
name: "Delete",
@ -770,43 +775,43 @@ export const defaultShortcuts = [
},
{
name: "Open playground",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + K`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + K`,
},
{
name: "Undo",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Z`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Z`,
},
{
name: "Redo",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + Y`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Y`,
},
{
name: "Group",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + G`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + G`,
},
{
name: "Cut",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + X`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + X`,
},
{
name: "Paste",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + V`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + V`,
},
{
name: "API",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + R`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + R`,
},
{
name: "Download",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + J`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + J`,
},
{
name: "Update",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + U`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + U`,
},
{
name: "Freeze",
shortcut: `${navigator.userAgent.toUpperCase().includes("MAC") ? "Cmd" : "Ctrl"} + F`,
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + F`,
},
];

View file

@ -1,4 +1,4 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import axios, { Axios, AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { renewAccessToken } from ".";
@ -83,8 +83,10 @@ function ApiInterceptor() {
(config) => {
const checkRequest = checkDuplicateRequestAndStoreRequest(config);
const controller = new AbortController();
if (!checkRequest) {
return Promise.reject("Duplicate request.");
controller.abort("Duplicate Request");
}
const accessToken = cookies.get("access_token_lf");
@ -92,7 +94,10 @@ function ApiInterceptor() {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
return config;
return {
...config,
signal: controller.signal,
};
},
(error) => {
return Promise.reject(error);

View file

@ -8,6 +8,7 @@ import {
APITemplateType,
Component,
LoginType,
ProfilePicturesTypeAPI,
Users,
VertexBuildTypeAPI,
VerticesOrderTypeAPI,
@ -364,6 +365,19 @@ export async function uploadFile(
return await api.post(`${BASE_URL_API}files/upload/${id}`, formData);
}
export async function getProfilePictures(): Promise<ProfilePicturesTypeAPI | null> {
try {
const res = await api.get(`${BASE_URL_API}files/profile_pictures/list`);
if (res.status === 200) {
return res.data;
}
} catch (error) {
throw error;
}
return null;
}
export async function postCustomComponent(
code: string,
apiClass: APIClassType,

View file

@ -12,13 +12,11 @@ import { StrictMode } from "react";
import "./style/classes.css";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
document.getElementById("root") as HTMLElement,
);
root.render(
<StrictMode>
<ContextWrapper>
<App />
</ContextWrapper>
</StrictMode>
<ContextWrapper>
<App />
</ContextWrapper>,
);
reportWebVitals();

View file

@ -1,14 +1,25 @@
import IconComponent from "../../../../../../../components/genericIconComponent";
import { Case } from "../../../../../../../shared/components/caseComponent";
import { FilePreviewType } from "../../../../../../../types/components";
import { classNames } from "../../../../../../../utils/utils";
type ButtonSendWrapperProps = {
send: () => void;
lockChat: boolean;
noInput: boolean;
saveLoading: boolean;
chatValue: string;
files: FilePreviewType[];
};
const ButtonSendWrapper = ({
send,
lockChat,
noInput,
saveLoading,
chatValue,
}) => {
files,
}: ButtonSendWrapperProps) => {
return (
<button
className={classNames(
@ -22,7 +33,11 @@ const ButtonSendWrapper = ({
disabled={lockChat || saveLoading}
onClick={(): void => send()}
>
<Case condition={lockChat || saveLoading}>
<Case
condition={
lockChat || saveLoading || files.some((file) => file.loading)
}
>
<IconComponent
name="Lock"
className="form-modal-lock-icon"
@ -38,7 +53,12 @@ const ButtonSendWrapper = ({
/>
</Case>
<Case condition={!(lockChat || saveLoading) && !noInput}>
<Case
condition={
!(lockChat || saveLoading || files.some((file) => file.loading)) &&
!noInput
}
>
<IconComponent
name="LucideSend"
className="form-modal-send-icon "

View file

@ -1,7 +1,17 @@
import ShortUniqueId from "short-unique-id";
import useFileUpload from "./use-file-upload";
import useAlertStore from "../../../../../../stores/alertStore";
const useDragAndDrop = (setIsDragging, setFiles, currentFlowId) => {
const fsErrorText =
"Please ensure your file has one of the following extensions:";
const snErrorTxt = "png, jpg, jpeg, gif, bmp, webp";
const useDragAndDrop = (
setIsDragging,
setFiles,
currentFlowId,
setErrorData,
) => {
const dragOver = (e) => {
e.preventDefault();
if (e.dataTransfer.types.some((type) => type === "Files")) {
@ -24,7 +34,7 @@ const useDragAndDrop = (setIsDragging, setFiles, currentFlowId) => {
const onDrop = (e) => {
e.preventDefault();
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files, setFiles, currentFlowId);
handleFiles(e.dataTransfer.files, setFiles, currentFlowId, setErrorData);
e.dataTransfer.clearData();
}
setIsDragging(false);
@ -37,8 +47,19 @@ const useDragAndDrop = (setIsDragging, setFiles, currentFlowId) => {
};
};
const handleFiles = (files, setFiles, currentFlowId) => {
const handleFiles = (files, setFiles, currentFlowId, setErrorData) => {
if (files) {
const allowedExtensions = ["png", "jpg", "jpeg", "gif", "bmp", "webp"];
const file = files?.[0];
const fileExtension = file.name.split(".").pop()?.toLowerCase();
if (!fileExtension || !allowedExtensions.includes(fileExtension)) {
console.log("Error uploading file");
setErrorData({
title: "Error uploading file",
list: [fsErrorText, snErrorTxt],
});
return;
}
const uid = new ShortUniqueId({ length: 3 });
const id = uid();
const type = files[0].type.split("/")[0];

View file

@ -89,6 +89,7 @@ export default function ChatInput({
noInput={noInput}
saveLoading={saveLoading}
chatValue={chatValue}
files={files}
/>
</div>

View file

@ -25,7 +25,7 @@ export default function ChatView({
setLockChat,
}: chatViewProps): JSX.Element {
const { flowPool, outputs, inputs, CleanFlowPool } = useFlowStore();
const { setNoticeData } = useAlertStore();
const { setErrorData } = useAlertStore();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const messagesRef = useRef<HTMLDivElement | null>(null);
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
@ -161,6 +161,7 @@ export default function ChatView({
setIsDragging,
setFiles,
currentFlowId,
setErrorData,
);
return (

View file

@ -208,7 +208,9 @@ export default function IOModal({
{outputs.length > 0 && (
<TabsTrigger value={"2"}>Outputs</TabsTrigger>
)}
{haveChat && <TabsTrigger value={"0"}>History</TabsTrigger>}
{haveChat && (
<TabsTrigger value={"0"}>Memories</TabsTrigger>
)}
</TabsList>
</div>

View file

@ -23,8 +23,6 @@ const useColumnDefs = (
: templateParam.name) ?? params.data.key
);
},
tooltipField: "display_name",
tooltipComponent: TableTooltipRender,
wrapText: true,
autoHeight: true,
flex: 1,
@ -35,7 +33,6 @@ const useColumnDefs = (
headerName: "Description",
field: "info",
tooltipField: "info",
tooltipComponent: TableTooltipRender,
wrapText: true,
autoHeight: true,
flex: 2,

View file

@ -11,7 +11,6 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { postCustomComponent } from "../../../../controllers/API";
import ConfirmationModal from "../../../../modals/confirmationModal";
import EditNodeModal from "../../../../modals/editNodeModal";
import ShareModal from "../../../../modals/shareModal";
@ -42,12 +41,8 @@ export default function NodeToolbarComponent({
showNode,
name = "code",
selected,
updateNodeCode,
setShowState,
onCloseAdvancedModal,
isOutdated,
// openWDoubleClick,
// setOpenWDoubleClick,
}: nodeToolbarPropsType): JSX.Element {
const nodeLength = Object.keys(data.node!.template).filter(
(templateField) =>
@ -62,16 +57,14 @@ export default function NodeToolbarComponent({
data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int" ||
data.node.template[templateField].type === "dict" ||
data.node.template[templateField].type === "NestedDict"),
data.node.template[templateField].type === "NestedDict")
).length;
const templates = useTypesStore((state) => state.templates);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const unselectAll = useFlowStore((state) => state.unselectAll);
function handleMinimizeWShortcut(e: KeyboardEvent) {
e.preventDefault();
if (isMinimal) {
@ -203,7 +196,7 @@ export default function NodeToolbarComponent({
const [showconfirmShare, setShowconfirmShare] = useState(false);
const [showOverrideModal, setShowOverrideModal] = useState(false);
const [flowComponent, setFlowComponent] = useState<FlowType>(
createFlowComponent(cloneDeep(data), version),
createFlowComponent(cloneDeep(data), version)
);
// useEffect(() => {
@ -222,7 +215,7 @@ export default function NodeToolbarComponent({
const updateNodeInternals = useUpdateNodeInternals();
const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection,
(state) => state.setLastCopiedSelection
);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -296,7 +289,7 @@ export default function NodeToolbarComponent({
nodes,
edges,
setNodes,
setEdges,
setEdges
);
break;
case "override":
@ -320,51 +313,20 @@ export default function NodeToolbarComponent({
y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position.x,
paneY: nodes.find((node) => node.id === data.id)?.position.y,
},
}
);
break;
case "update":
takeSnapshot();
// to update we must get the code from the templates in useTypesStore
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code.value;
if (data.node) {
postCustomComponent(currentCode, data.node)
.then((apiReturn) => {
const { data } = apiReturn;
if (data && updateNodeCode) {
updateNodeCode(data, currentCode, "code");
}
})
.catch((err) => {
console.log(err);
});
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...data,
};
newNode.data.node.template.code.value = currentCode;
return newNode;
});
}
break;
}
};
const isSaved = flows.some((flow) =>
Object.values(flow).includes(data.node?.display_name!),
Object.values(flow).includes(data.node?.display_name!)
);
const setNode = useFlowStore((state) => state.setNode);
const handleOnNewValue = (
newValue: string | string[] | boolean | Object[],
newValue: string | string[] | boolean | Object[]
): void => {
if (data.node!.template[name].value !== newValue) {
takeSnapshot();
@ -463,7 +425,7 @@ export default function NodeToolbarComponent({
<ShadTooltip content="Freeze" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)}
onClick={(event) => {
event.preventDefault();
@ -484,7 +446,7 @@ export default function NodeToolbarComponent({
className={cn(
"h-4 w-4 transition-all",
// TODO UPDATE THIS COLOR TO BE A VARIABLE
frozen ? "animate-wiggle text-ice" : "",
frozen ? "animate-wiggle text-ice" : ""
)}
/>
</button>
@ -512,7 +474,7 @@ export default function NodeToolbarComponent({
<div
data-testid="more-options-modal"
className={classNames(
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)}
>
<IconComponent
@ -530,7 +492,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Code")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Code"}
icon={"Code"}
dataTestId="code-button-modal"
@ -544,7 +505,6 @@ export default function NodeToolbarComponent({
shortcuts.find((obj) => obj.name === "Advanced Settings")
?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Advanced"}
icon={"Settings2"}
dataTestId="edit-button-modal"
@ -556,7 +516,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Save")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Save"}
icon={"SaveAll"}
dataTestId="save-button-modal"
@ -567,7 +526,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Duplicate"}
icon={"Copy"}
dataTestId="copy-button-modal"
@ -578,26 +536,11 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Copy")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Copy"}
icon={"Clipboard"}
dataTestId="copy-button-modal"
/>
</SelectItem>
{isOutdated && (
<SelectItem value={"update"}>
<ToolbarSelectItem
shortcut={
shortcuts.find((obj) => obj.name === "Update")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Update"}
icon={"Code"}
dataTestId="update-button-modal"
ping={isOutdated}
/>
</SelectItem>
)}
{hasStore && (
<SelectItem
value={"Share"}
@ -607,7 +550,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Share")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Share"}
icon={"Share3"}
dataTestId="share-button-modal"
@ -621,7 +563,6 @@ export default function NodeToolbarComponent({
shortcuts.find((obj) => obj.name === "Download")
?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Download"}
icon={"Download"}
dataTestId="Download-button-modal"
@ -636,7 +577,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Docs")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Docs"}
icon={"FileText"}
dataTestId="docs-button-modal"
@ -649,7 +589,6 @@ export default function NodeToolbarComponent({
shortcuts.find((obj) => obj.name === "Minimize")
?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={showNode ? "Minimize" : "Expand"}
icon={showNode ? "Minimize2" : "Maximize2"}
dataTestId="minimize-button-modal"
@ -662,7 +601,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Group")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Ungroup"}
icon={"Ungroup"}
dataTestId="group-button-modal"
@ -674,7 +612,6 @@ export default function NodeToolbarComponent({
shortcut={
shortcuts.find((obj) => obj.name === "Freeze")?.shortcut!
}
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
value={"Freeze"}
icon={"Snowflake"}
dataTestId="group-button-modal"

View file

@ -8,7 +8,6 @@ export default function ToolbarSelectItem({
dataTestId,
ping,
shortcut,
isMac,
}: toolbarSelectItemProps) {
let hasShift = false;
const fixedShortcut = shortcut?.split("+");
@ -18,7 +17,7 @@ export default function ToolbarSelectItem({
}
});
const filteredShortcut = fixedShortcut.filter(
(key) => !key.toLowerCase().includes("shift"),
(key) => !key.toLowerCase().includes("shift")
);
let shortcutWPlus = "";
if (!hasShift) shortcutWPlus = filteredShortcut.join("+");

View file

@ -2,7 +2,7 @@ import * as Form from "@radix-ui/react-form";
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import GradientChooserComponent from "../../components/gradientChooserComponent";
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
import Header from "../../components/headerComponent";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
@ -24,11 +24,11 @@ import {
import { gradients } from "../../utils/styleUtils";
export default function ProfileSettingsPage(): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
(state) => state.setCurrentFlowId,
);
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE
CONTROL_PATCH_USER_STATE,
);
// set null id

View file

@ -1,68 +0,0 @@
import * as Form from "@radix-ui/react-form";
import GradientChooserComponent from "../../../../../../components/gradientChooserComponent";
import { Button } from "../../../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
import { gradients } from "../../../../../../utils/styleUtils";
type ProfileGradientFormComponentProps = {
gradient: string;
handleInput: (event: any) => void;
handlePatchGradient: (gradient: string) => void;
userData: any;
};
const ProfileGradientFormComponent = ({
gradient,
handleInput,
handlePatchGradient,
userData,
}: ProfileGradientFormComponentProps) => {
return (
<>
<Form.Root
onSubmit={(event) => {
handlePatchGradient(gradient);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: gradient
}
onChange={(value) => {
handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
</>
);
};
export default ProfileGradientFormComponent;

View file

@ -0,0 +1,26 @@
import { reject } from "lodash";
import { PROFILE_PICTURES_GET_ERROR_ALERT } from "../../../../../../../../../constants/alerts_constants";
import { getProfilePictures } from "../../../../../../../../../controllers/API";
import axios from "axios";
const useGetProfilePictures = (setErrorData) => {
const handleGetProfilePictures = async () => {
try {
const profilePictures = await getProfilePictures();
return profilePictures!.files;
} catch (error) {
if (axios.isCancel(error)) {
console.warn("Request canceled: ", error.message);
} else {
setErrorData({
title: PROFILE_PICTURES_GET_ERROR_ALERT,
list: [(error as any)?.response?.data?.detail],
});
}
}
};
return { handleGetProfilePictures };
};
export default useGetProfilePictures;

View file

@ -0,0 +1,84 @@
import { useEffect, useRef, useState } from "react";
import useAlertStore from "../../../../../../../../stores/alertStore";
import { gradients } from "../../../../../../../../utils/styleUtils";
import useGetProfilePictures from "./hooks/use-get-profile-pictures";
import { Label } from "../../../../../../../../components/ui/label";
import {
BACKEND_URL,
BASE_URL_API,
} from "../../../../../../../../constants/constants";
import HorizontalScrollFadeComponent from "../../../../../../../../components/horizontalScrollFadeComponent";
import LoadingComponent from "../../../../../../../../components/loadingComponent";
import Loading from "../../../../../../../../components/ui/loading";
import { cn } from "../../../../../../../../utils/utils";
import { Button } from "../../../../../../../../components/ui/button";
import { useDarkStore } from "../../../../../../../../stores/darkStore";
type ProfilePictureChooserComponentProps = {
profilePictures: { [key: string]: string[] };
loading: boolean;
value: string;
onChange: (value: string) => void;
};
export default function ProfilePictureChooserComponent({
profilePictures,
loading,
value,
onChange,
}: ProfilePictureChooserComponentProps) {
const ref = useRef<HTMLButtonElement>(null);
const dark = useDarkStore((state) => state.dark);
useEffect(() => {
if (value && ref) {
ref.current?.scrollIntoView({ behavior: "smooth", block: "center" });
}
}, [ref]);
return (
<div className="flex flex-col justify-center gap-2">
{loading ? (
<Loading />
) : (
Object.keys(profilePictures).map((folder, idx) => (
<div className="flex flex-col gap-2">
<div className="edit-flow-arrangement">
<span className="font-normal">{folder}</span>
</div>
<div className="block overflow-hidden">
<div className="flex items-center gap-1 overflow-x-auto rounded-lg bg-background px-1">
{profilePictures[folder].map((path, idx) => (
<Button
ref={value === folder + "/" + path ? ref : undefined}
size="none"
variant="none"
onClick={() => onChange(folder + "/" + path)}
className="shrink-0 px-0.5 py-2"
>
<img
key={idx}
src={`${BACKEND_URL.slice(
0,
BACKEND_URL.length - 1,
)}${BASE_URL_API}files/profile_pictures/${folder + "/" + path}`}
style={{
filter:
value === folder + "/" + path
? dark
? "drop-shadow(0 0 0.3rem rgb(255, 255, 255))"
: "drop-shadow(0 0 0.3rem rgb(0, 0, 0))"
: "",
}}
className={cn("h-12 w-12")}
/>
</Button>
))}
</div>
</div>
</div>
))
)}
</div>
);
}

View file

@ -0,0 +1,109 @@
import * as Form from "@radix-ui/react-form";
import { Button } from "../../../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
import { gradients } from "../../../../../../utils/styleUtils";
import ProfilePictureChooserComponent from "./components/profilePictureChooserComponent";
import { useEffect, useState } from "react";
import { GenericAbortSignal } from "axios";
type ProfilePictureFormComponentProps = {
profilePicture: string;
handleInput: (event: any) => void;
handlePatchProfilePicture: (gradient: string) => void;
handleGetProfilePictures: () => Promise<string[] | undefined>;
userData: any;
};
const ProfilePictureFormComponent = ({
profilePicture,
handleInput,
handlePatchProfilePicture,
handleGetProfilePictures,
userData,
}: ProfilePictureFormComponentProps) => {
const [profilePictures, setProfilePictures] = useState<{
[key: string]: string[];
}>({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const abortController = new AbortController();
handleGetProfilePictures()
.then((data) => {
if (data) {
data.forEach((profile_picture) => {
const [folder, path] = profile_picture.split("/");
setProfilePictures((prev) => {
if (prev[folder]) {
prev[folder].push(path);
} else {
prev[folder] = [path];
}
return prev;
});
setLoading(false);
});
}
})
.catch(() => {
setLoading(false);
});
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
return (
<Form.Root
onSubmit={(event) => {
handlePatchProfilePicture(profilePicture);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Picture</CardTitle>
<CardDescription>
Choose the image that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<ProfilePictureChooserComponent
profilePictures={profilePictures}
loading={loading}
value={
profilePicture == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: profilePicture
}
onChange={(value) => {
handleInput({ target: { name: "profilePicture", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
);
};
export default ProfilePictureFormComponent;

View file

@ -9,14 +9,15 @@ import {
inputHandlerEventType,
patchUserInputStateType,
} from "../../../../types/components";
import usePatchGradient from "../hooks/use-patch-gradient";
import usePatchProfilePicture from "../hooks/use-patch-profile-picture";
import usePatchPassword from "../hooks/use-patch-password";
import useSaveKey from "../hooks/use-save-key";
import useScrollToElement from "../hooks/use-scroll-to-element";
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
import PasswordFormComponent from "./components/PasswordForm";
import ProfileGradientFormComponent from "./components/ProfileGradientForm";
import ProfilePictureFormComponent from "./components/ProfilePictureForm";
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
import useGetProfilePictures from "./components/ProfilePictureForm/components/profilePictureChooserComponent/hooks/use-get-profile-pictures";
export default function GeneralPage() {
const setCurrentFlowId = useFlowsManagerStore(
@ -42,7 +43,7 @@ export default function GeneralPage() {
const loadingApiKey = useStoreStore((state) => state.loadingApiKey);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
const { password, cnfPassword, gradient, apikey } = inputState;
const { password, cnfPassword, profilePicture, apikey } = inputState;
const { handlePatchPassword } = usePatchPassword(
userData,
@ -50,7 +51,9 @@ export default function GeneralPage() {
setErrorData,
);
const { handlePatchGradient } = usePatchGradient(
const { handleGetProfilePictures } = useGetProfilePictures(setErrorData);
const { handlePatchProfilePicture } = usePatchProfilePicture(
setSuccessData,
setErrorData,
userData,
@ -78,10 +81,11 @@ export default function GeneralPage() {
<GeneralPageHeaderComponent />
<div className="grid gap-6">
<ProfileGradientFormComponent
gradient={gradient}
<ProfilePictureFormComponent
profilePicture={profilePicture}
handleInput={handleInput}
handlePatchGradient={handlePatchGradient}
handlePatchProfilePicture={handlePatchProfilePicture}
handleGetProfilePictures={handleGetProfilePictures}
userData={userData}
/>

View file

@ -5,18 +5,20 @@ import {
} from "../../../../constants/alerts_constants";
import { updateUser } from "../../../../controllers/API";
const usePatchGradient = (
const usePatchProfilePicture = (
setSuccessData,
setErrorData,
currentUserData,
setUserData,
) => {
const handlePatchGradient = async (gradient) => {
const handlePatchProfilePicture = async (profile_picture) => {
try {
if (gradient !== "") {
await updateUser(currentUserData.id, { profile_image: gradient });
if (profile_picture !== "") {
await updateUser(currentUserData.id, {
profile_image: profile_picture,
});
let newUserData = cloneDeep(currentUserData);
newUserData.profile_image = gradient;
newUserData.profile_image = profile_picture;
setUserData(newUserData);
}
setSuccessData({ title: SAVE_SUCCESS_ALERT });
@ -30,8 +32,8 @@ const usePatchGradient = (
return {
currentUserData,
handlePatchGradient,
handlePatchProfilePicture,
};
};
export default usePatchGradient;
export default usePatchProfilePicture;

View file

@ -315,8 +315,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
targetHandle,
id,
data: cloneDeep(edge.data),
style: { stroke: "#555" },
className: "stroke-gray-900 ",
selected: false,
},
newEdges.map((edge) => ({ ...edge, selected: false })),
@ -396,8 +394,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
targetHandle: scapeJSONParse(connection.targetHandle!),
sourceHandle: scapeJSONParse(connection.sourceHandle!),
},
style: { stroke: "#555" },
className: "stroke-foreground stroke-connection",
// style: { stroke: "#555" },
// className: "stroke-foreground stroke-connection",
},
oldEdges,
);

View file

@ -74,7 +74,7 @@
--ice: #60a5fa;
--hover: #1a202e;
--disabled-run: #6366f1;
--selected: #2196f3;
--selected: #0369a1;
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
@ -132,7 +132,7 @@
--chat-send: #059669;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
--connection: #6d6c6c;
--beta-background: rgb(37 99 235);
--beta-foreground: rgb(219 234 254);

View file

@ -93,6 +93,10 @@ export type UploadFileTypeAPI = {
flowId: string;
};
export type ProfilePicturesTypeAPI = {
files: string[];
};
export type LoginType = {
grant_type?: string;
username: string;

View file

@ -396,7 +396,7 @@ export type loginInputStateType = {
export type patchUserInputStateType = {
password: string;
cnfPassword: string;
gradient: string;
profilePicture: string;
apikey: string;
};
@ -491,7 +491,7 @@ export type ChatInputType = {
isDragging: boolean;
files: FilePreviewType[];
setFiles: (
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[]),
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[])
) => void;
chatValue: string;
inputRef: {
@ -546,13 +546,7 @@ export type nodeToolbarPropsType = {
openAdvancedModal?: boolean;
onCloseAdvancedModal?: (close: boolean) => void;
selected: boolean;
updateNodeCode?: (
newNodeClass: APIClassType,
code: string,
name: string,
) => void;
setShowState: (show: boolean | SetStateAction<boolean>) => void;
isOutdated?: boolean;
};
export type parsedDataType = {
@ -599,7 +593,7 @@ export type chatMessagePropsType = {
updateChat: (
chat: ChatMessageType,
message: string,
stream_url?: string,
stream_url?: string
) => void;
};
@ -691,12 +685,12 @@ export type codeTabsPropsType = {
value: string,
node: NodeType,
template: TemplateVariableType,
tweak: tweakType,
tweak: tweakType
) => string;
buildTweakObject?: (
tw: string,
changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType,
template: TemplateVariableType
) => Promise<string | void>;
};
activeTweaks?: boolean;
@ -778,7 +772,6 @@ export type IOFileInputProps = {
};
export type toolbarSelectItemProps = {
isMac: boolean;
value: string;
icon: string;
style?: string;

View file

@ -11,6 +11,7 @@ import ShortUniqueId from "short-unique-id";
import getFieldTitle from "../CustomNodes/utils/get-field-title";
import {
INPUT_TYPES,
IS_MAC,
LANGFLOW_SUPPORTED_TYPES,
OUTPUT_TYPES,
SUCCESS_BUILD,
@ -99,18 +100,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
nodes: Node[],
edges: Edge[],
edges: Edge[]
) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if (
targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType,
(n) => n === sourceHandleObject.dataType
) ||
sourceHandleObject.baseClasses.some(
(t) =>
targetHandleObject.inputTypes?.some((n) => n === t) ||
t === targetHandleObject.type,
t === targetHandleObject.type
)
) {
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
@ -143,7 +144,7 @@ export function removeApiKeys(flow: FlowType): FlowType {
export function updateTemplate(
reference: APITemplateType,
objectToUpdate: APITemplateType,
objectToUpdate: APITemplateType
): APITemplateType {
let clonedObject: APITemplateType = cloneDeep(reference);
@ -179,7 +180,7 @@ export const processFlows = (DbData: FlowType[], skipUpdate = true) => {
] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
return;
}
if (!skipUpdate) processDataFromFlow(flow, false);
processDataFromFlow(flow, !skipUpdate);
} catch (e) {
console.log(e);
}
@ -203,7 +204,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
export function updateIds(
{ edges, nodes }: { edges: Edge[]; nodes: Node[] },
selection?: { edges: Edge[]; nodes: Node[] },
selection?: { edges: Edge[]; nodes: Node[] }
) {
let idsMap = {};
const selectionIds = selection?.nodes.map((n) => n.id);
@ -231,7 +232,7 @@ export function updateIds(
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!,
edge.sourceHandle!
);
edge.sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
@ -241,7 +242,7 @@ export function updateIds(
edge.data.sourceHandle.id = edge.source;
}
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!,
edge.targetHandle!
);
edge.targetHandle = scapedJSONStringfy({
...targetHandleObject,
@ -287,11 +288,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id,
node.id
)
) {
errors.push(
`${displayName || type} is missing ${getFieldTitle(template, t)}.`,
`${displayName || type} is missing ${getFieldTitle(template, t)}.`
);
} else if (
template[t].type === "dict" &&
@ -305,15 +306,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
errors.push(
`${displayName || type} (${getFieldTitle(
template,
t,
)}) contains duplicate keys with the same values.`,
t
)}) contains duplicate keys with the same values.`
);
if (hasEmptyKey(template[t].value))
errors.push(
`${displayName || type} (${getFieldTitle(
template,
t,
)}) field must not be empty.`,
t
)}) field must not be empty.`
);
}
return errors;
@ -322,7 +323,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
export function validateNodes(
nodes: Node[],
edges: Edge[],
edges: Edge[]
): // this returns an array of tuples with the node id and the errors
Array<{ id: string; errors: Array<string> }> {
if (nodes.length === 0) {
@ -343,9 +344,9 @@ export function updateEdges(edges: Edge[]) {
if (edges)
edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!,
edge.targetHandle!
);
edge.className = "stroke-gray-900 stroke-connection";
edge.className = "";
});
}
@ -410,7 +411,7 @@ export function handleKeyDown(
| React.KeyboardEvent<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>,
inputValue: string | string[] | null,
block: string,
block: string
) {
//condition to fix bug control+backspace on Windows/Linux
if (
@ -420,9 +421,7 @@ export function handleKeyDown(
(inputValue === block ||
inputValue?.charAt(inputValue?.length - 1) === " " ||
specialCharsRegex.test(inputValue?.charAt(inputValue?.length - 1)))) ||
(navigator.userAgent.toUpperCase().includes("MAC") &&
e.ctrlKey === true &&
e.key === "Backspace")
(IS_MAC && e.ctrlKey === true && e.key === "Backspace")
) {
e.preventDefault();
e.stopPropagation();
@ -435,7 +434,7 @@ export function handleKeyDown(
}
export function handleOnlyIntegerInput(
event: React.KeyboardEvent<HTMLInputElement>,
event: React.KeyboardEvent<HTMLInputElement>
) {
if (
event.key === "." ||
@ -451,7 +450,7 @@ export function handleOnlyIntegerInput(
export function getConnectedNodes(
edge: Edge,
nodes: Array<NodeType>,
nodes: Array<NodeType>
): Array<NodeType> {
const sourceId = edge.source;
const targetId = edge.target;
@ -552,7 +551,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
!edge.sourceHandle ||
!edge.targetHandle ||
!edge.sourceHandle.includes("{") ||
!edge.targetHandle.includes("{"),
!edge.targetHandle.includes("{")
);
}
@ -575,7 +574,7 @@ export function customStringify(obj: any): string {
const keys = Object.keys(obj).sort();
const keyValuePairs = keys.map(
(key) => `"${key}":${customStringify(obj[key])}`,
(key) => `"${key}":${customStringify(obj[key])}`
);
return `{${keyValuePairs.join(",")}}`;
}
@ -604,7 +603,7 @@ export function getHandleId(
source: string,
sourceHandle: string,
target: string,
targetHandle: string,
targetHandle: string
) {
return (
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
@ -615,7 +614,7 @@ export function generateFlow(
selection: OnSelectionChangeParams,
nodes: Node[],
edges: Edge[],
name: string,
name: string
): generateFlowType {
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
const uid = new ShortUniqueId({ length: 5 });
@ -624,7 +623,7 @@ export function generateFlow(
newFlowData.edges = selection.edges.filter(
(edge) =>
selection.nodes.some((node) => node.id === edge.target) &&
selection.nodes.some((node) => node.id === edge.source),
selection.nodes.some((node) => node.id === edge.source)
);
newFlowData.nodes = selection.nodes;
@ -645,7 +644,7 @@ export function generateFlow(
(edge) =>
(selection.nodes.some((node) => node.id === edge.target) ||
selection.nodes.some((node) => node.id === edge.source)) &&
newFlowData.edges.every((e) => e.id !== edge.id),
newFlowData.edges.every((e) => e.id !== edge.id)
),
};
}
@ -656,13 +655,13 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
const { nodes, edges } = groupNode.data.node!.flow!.data!;
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
newEdges = newEdges.filter(
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id),
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id)
);
newEdges.forEach((edge) => {
if (lastNode && edge.source === lastNode.id) {
edge.source = groupNode.id;
let newSourceHandle: sourceHandleType = scapeJSONParse(
edge.sourceHandle!,
edge.sourceHandle!
);
newSourceHandle.id = groupNode.id;
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
@ -689,7 +688,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
export function filterFlow(
selection: OnSelectionChangeParams,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) {
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
@ -727,7 +726,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
export function concatFlows(
flow: FlowType,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) {
const { nodes, edges } = flow.data!;
setNodes((old) => [...old, ...nodes]);
@ -736,7 +735,7 @@ export function concatFlows(
export function validateSelection(
selection: OnSelectionChangeParams,
edges: Edge[],
edges: Edge[]
): Array<string> {
const clonedSelection = cloneDeep(selection);
const clonedEdges = cloneDeep(edges);
@ -750,7 +749,7 @@ export function validateSelection(
let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
// then filter the edges that are connected to the nodes in the set
let connectedEdges = clonedSelection.edges.filter(
(e) => nodesSet.has(e.source) && nodesSet.has(e.target),
(e) => nodesSet.has(e.source) && nodesSet.has(e.target)
);
// add the edges to the selection
clonedSelection.edges = connectedEdges;
@ -764,17 +763,17 @@ export function validateSelection(
clonedSelection.nodes.some(
(node) =>
isInputNode(node.data as NodeDataType) ||
isOutputNode(node.data as NodeDataType),
isOutputNode(node.data as NodeDataType)
)
) {
errorsArray.push(
"Please select only nodes that are not input or output nodes",
"Please select only nodes that are not input or output nodes"
);
}
//check if there are two or more nodes with free outputs
if (
clonedSelection.nodes.filter(
(n) => !clonedSelection.edges.some((e) => e.source === n.id),
(n) => !clonedSelection.edges.some((e) => e.source === n.id)
).length > 1
) {
errorsArray.push("Please select only one node with free outputs");
@ -785,7 +784,7 @@ export function validateSelection(
clonedSelection.nodes.some(
(node) =>
!clonedSelection.edges.some((edge) => edge.target === node.id) &&
!clonedSelection.edges.some((edge) => edge.source === node.id),
!clonedSelection.edges.some((edge) => edge.source === node.id)
)
) {
errorsArray.push("Please select only nodes that are connected");
@ -842,8 +841,8 @@ export function mergeNodeTemplates({
nodeTemplate[key].display_name
? nodeTemplate[key].display_name
: nodeTemplate[key].name
? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key);
? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key);
}
}
});
@ -854,7 +853,7 @@ function isHandleConnected(
edges: Edge[],
key: string,
field: TemplateVariableType,
nodeId: string,
nodeId: string
) {
/*
this function receives a flow and a handleId and check if there is a connection with this handle
@ -870,7 +869,7 @@ function isHandleConnected(
id: nodeId,
proxy: { id: field.proxy!.id, field: field.proxy!.field },
inputTypes: field.input_types,
} as targetHandleType),
} as targetHandleType)
)
) {
return true;
@ -885,7 +884,7 @@ function isHandleConnected(
fieldName: key,
id: nodeId,
inputTypes: field.input_types,
} as targetHandleType),
} as targetHandleType)
)
) {
return true;
@ -908,7 +907,7 @@ export function generateNodeTemplate(Flow: FlowType) {
export function generateNodeFromFlow(
flow: FlowType,
getNodeId: (type: string) => string,
getNodeId: (type: string) => string
): NodeType {
const { nodes } = flow.data!;
const outputNode = cloneDeep(findLastNode(flow.data!));
@ -939,7 +938,7 @@ export function generateNodeFromFlow(
export function connectedInputNodesOnHandle(
nodeId: string,
handleId: string,
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] },
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
) {
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
[];
@ -976,7 +975,7 @@ export function connectedInputNodesOnHandle(
export function updateProxyIdsOnTemplate(
template: APITemplateType,
idsMap: { [key: string]: string },
idsMap: { [key: string]: string }
) {
Object.keys(template).forEach((key) => {
if (template[key].proxy && idsMap[template[key].proxy!.id]) {
@ -987,7 +986,7 @@ export function updateProxyIdsOnTemplate(
export function updateEdgesIds(
edges: Edge[],
idsMap: { [key: string]: string },
idsMap: { [key: string]: string }
) {
edges.forEach((edge) => {
let targetHandle: targetHandleType = edge.data.targetHandle;
@ -1005,11 +1004,6 @@ export function processFlowEdges(flow: FlowType) {
const newEdges = updateEdgesHandleIds(flow.data);
flow.data.edges = newEdges;
}
//update edges colors
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555" };
});
}
export function expandGroupNode(
@ -1019,7 +1013,7 @@ export function expandGroupNode(
nodes: Node[],
edges: Edge[],
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) {
const idsMap = updateIds(flow!.data!);
updateProxyIdsOnTemplate(template, idsMap);
@ -1062,7 +1056,7 @@ export function expandGroupNode(
const lastNode = cloneDeep(findLastNode(flow!.data!));
newEdge.source = lastNode!.id;
let newSourceHandle: sourceHandleType = scapeJSONParse(
newEdge.sourceHandle!,
newEdge.sourceHandle!
);
newSourceHandle.id = lastNode!.id;
newEdge.data.sourceHandle = newSourceHandle;
@ -1119,7 +1113,7 @@ export function expandGroupNode(
export function getGroupStatus(
flow: FlowType,
ssData: { [key: string]: { valid: boolean; params: string } },
ssData: { [key: string]: { valid: boolean; params: string } }
) {
let status = { valid: true, params: SUCCESS_BUILD };
const { nodes } = flow.data!;
@ -1138,7 +1132,7 @@ export function getGroupStatus(
export function createFlowComponent(
nodeData: NodeDataType,
version: string,
version: string
): FlowType {
const flowNode: FlowType = {
data: {
@ -1174,7 +1168,7 @@ export function downloadNode(NodeFLow: FlowType) {
export function updateComponentNameAndType(
data: any,
component: NodeDataType,
component: NodeDataType
) {}
export function removeFileNameFromComponents(flow: FlowType) {
@ -1248,7 +1242,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) {
export function downloadFlow(
flow: FlowType,
flowName: string,
flowDescription?: string,
flowDescription?: string
) {
let clonedFlow = cloneDeep(flow);
removeFileNameFromComponents(clonedFlow);
@ -1258,7 +1252,7 @@ export function downloadFlow(
...clonedFlow,
name: flowName,
description: flowDescription,
}),
})
)}`;
// create a link element and set its properties
@ -1273,7 +1267,7 @@ export function downloadFlow(
export function downloadFlows() {
downloadFlowsFromDatabase().then((flows) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows),
JSON.stringify(flows)
)}`;
// create a link element and set its properties
@ -1297,7 +1291,7 @@ export function getRandomDescription(): string {
export const createNewFlow = (
flowData: ReactFlowJsonObject,
flow: FlowType,
folderId: string,
folderId: string
) => {
return {
description: flow?.description ?? getRandomDescription(),

View file

@ -116,6 +116,7 @@ import {
Settings,
Settings2,
Share,
AlertTriangle,
Share2,
Shield,
Sliders,
@ -434,6 +435,7 @@ export const nodeIconsLucide: iconsType = {
SunIcon,
MoonIcon,
Bell,
AlertTriangle,
ChevronLeft,
SlidersHorizontal,
Palette,

View file

@ -1,7 +1,6 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
import { APIDataType, TemplateVariableType } from "../types/api";
import {
groupedObjType,
@ -10,6 +9,7 @@ import {
} from "../types/components";
import { NodeType } from "../types/flow";
import { FlowState } from "../types/tabs";
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
export function classNames(...classes: Array<string>): string {
return classes.filter(Boolean).join(" ");

View file

@ -227,4 +227,8 @@ test("user must be able to send an image on chat", async ({ page }) => {
await page.getByTestId("icon-LucideSend").click();
await page.waitForTimeout(2000);
await page.getByText("chain.png").isVisible();
await page.getByText("Close", { exact: true }).click();
await page.getByTestId("icon-ScanEye").last().click();
await page.getByText("Restart").isHidden();
});