Merge branch 'shortcuts_settings' of github.com:langflow-ai/langflow into shortcuts_settings
This commit is contained in:
commit
93ba523dee
134 changed files with 693 additions and 403 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
BIN
src/frontend/src/assets/profile-circle.png
Normal file
BIN
src/frontend/src/assets/profile-circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" : "",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export default function ChatInput({
|
|||
noInput={noInput}
|
||||
saveLoading={saveLoading}
|
||||
chatValue={chatValue}
|
||||
files={files}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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("+");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ export type UploadFileTypeAPI = {
|
|||
flowId: string;
|
||||
};
|
||||
|
||||
export type ProfilePicturesTypeAPI = {
|
||||
files: string[];
|
||||
};
|
||||
|
||||
export type LoginType = {
|
||||
grant_type?: string;
|
||||
username: string;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(" ");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue