merge fix
This commit is contained in:
commit
3a6cd8822c
44 changed files with 1071 additions and 1102 deletions
|
|
@ -55,6 +55,7 @@ class ApiKeyRead(ApiKeyBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
api_key: str = Field(schema_extra={"validate_default": True})
|
api_key: str = Field(schema_extra={"validate_default": True})
|
||||||
user_id: UUID = Field()
|
user_id: UUID = Field()
|
||||||
|
created_at: datetime = Field()
|
||||||
|
|
||||||
@field_validator("api_key")
|
@field_validator("api_key")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
18
src/frontend/package-lock.json
generated
18
src/frontend/package-lock.json
generated
|
|
@ -44,6 +44,7 @@
|
||||||
"debounce-promise": "^3.1.2",
|
"debounce-promise": "^3.1.2",
|
||||||
"dompurify": "^3.0.5",
|
"dompurify": "^3.0.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
"esbuild": "^0.17.19",
|
"esbuild": "^0.17.19",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.0.6",
|
"framer-motion": "^11.0.6",
|
||||||
|
|
@ -479,6 +480,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
|
"extraneous": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -5997,9 +5999,9 @@
|
||||||
"integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA=="
|
"integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA=="
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||||
},
|
},
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
|
|
@ -12204,6 +12206,16 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"node_modules/string-width/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
"debounce-promise": "^3.1.2",
|
"debounce-promise": "^3.1.2",
|
||||||
"dompurify": "^3.0.5",
|
"dompurify": "^3.0.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
"esbuild": "^0.17.19",
|
"esbuild": "^0.17.19",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.0.6",
|
"framer-motion": "^11.0.6",
|
||||||
|
|
|
||||||
|
|
@ -164,3 +164,13 @@ body {
|
||||||
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
|
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #bbb;
|
background-color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This CSS is to not apply the border for the column having 'no-border' class */
|
||||||
|
.no-border.ag-cell:focus {
|
||||||
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.no-border.ag-cell {
|
||||||
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ export default function Dropdown({
|
||||||
|
|
||||||
const refButton = useRef<HTMLButtonElement>(null);
|
const refButton = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const PopoverContentDropdown = children
|
const PopoverContentDropdown =
|
||||||
? PopoverContent
|
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
|
||||||
: PopoverContentWithoutPortal;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -181,18 +181,6 @@ export default function Header(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AlertDropdown>
|
</AlertDropdown>
|
||||||
{autoLogin && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
navigate("/account/api-keys");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="Key"
|
|
||||||
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ import {
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "../../../ui/command";
|
} from "../../../ui/command";
|
||||||
import { Input } from "../../../ui/input";
|
import { Input } from "../../../ui/input";
|
||||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverContentWithoutPortal,
|
||||||
|
} from "../../../ui/popover";
|
||||||
const CustomInputPopover = ({
|
const CustomInputPopover = ({
|
||||||
id,
|
id,
|
||||||
refInput,
|
refInput,
|
||||||
|
|
@ -39,6 +43,9 @@ const CustomInputPopover = ({
|
||||||
showOptions,
|
showOptions,
|
||||||
}) => {
|
}) => {
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
|
const PopoverContentInput = editNode
|
||||||
|
? PopoverContent
|
||||||
|
: PopoverContentWithoutPortal;
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
if (password) {
|
if (password) {
|
||||||
|
|
@ -107,7 +114,7 @@ const CustomInputPopover = ({
|
||||||
data-testid={editNode ? id + "-edit" : id}
|
data-testid={editNode ? id + "-edit" : id}
|
||||||
/>
|
/>
|
||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<PopoverContentWithoutPortal
|
<PopoverContentInput
|
||||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
|
|
@ -184,7 +191,7 @@ const CustomInputPopover = ({
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContentWithoutPortal>
|
</PopoverContentInput>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@ import {
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "../../../ui/command";
|
} from "../../../ui/command";
|
||||||
import { Input } from "../../../ui/input";
|
import { Input } from "../../../ui/input";
|
||||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverContentWithoutPortal,
|
||||||
|
} from "../../../ui/popover";
|
||||||
const CustomInputPopoverObject = ({
|
const CustomInputPopoverObject = ({
|
||||||
id,
|
id,
|
||||||
refInput,
|
refInput,
|
||||||
|
|
@ -23,6 +27,7 @@ const CustomInputPopoverObject = ({
|
||||||
disabled,
|
disabled,
|
||||||
setShowOptions,
|
setShowOptions,
|
||||||
required,
|
required,
|
||||||
|
editNode,
|
||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -34,6 +39,10 @@ const CustomInputPopoverObject = ({
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
showOptions,
|
showOptions,
|
||||||
}) => {
|
}) => {
|
||||||
|
const PopoverContentInput = editNode
|
||||||
|
? PopoverContent
|
||||||
|
: PopoverContentWithoutPortal;
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
onChange && onChange(e.target.value);
|
onChange && onChange(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
@ -79,7 +88,7 @@ const CustomInputPopoverObject = ({
|
||||||
data-testid={id}
|
data-testid={id}
|
||||||
/>
|
/>
|
||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<PopoverContentWithoutPortal
|
<PopoverContentInput
|
||||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
|
|
@ -159,7 +168,7 @@ const CustomInputPopoverObject = ({
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContentWithoutPortal>
|
</PopoverContentInput>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ export default function InputComponent({
|
||||||
setSelectedOptions={setSelectedOptions}
|
setSelectedOptions={setSelectedOptions}
|
||||||
options={objectOptions}
|
options={objectOptions}
|
||||||
value={value}
|
value={value}
|
||||||
|
editNode={editNode}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
setShowOptions={setShowOptions}
|
setShowOptions={setShowOptions}
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ export default function InputGlobalComponent({
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.node?.template[name])
|
if (data)
|
||||||
if (
|
if (
|
||||||
globalVariablesEntries &&
|
globalVariablesEntries &&
|
||||||
!globalVariablesEntries.includes(data.node?.template[name].value) &&
|
!globalVariablesEntries.includes(data.value) &&
|
||||||
data.node?.template[name].load_from_db
|
data.load_from_db
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onChange("", true);
|
onChange("", true);
|
||||||
|
|
@ -46,17 +46,11 @@ export default function InputGlobalComponent({
|
||||||
}, [globalVariablesEntries]);
|
}, [globalVariablesEntries]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!data.value && data.display_name) {
|
||||||
!data.node?.template[name].value &&
|
if (unavaliableFields[data.display_name!] && !disabled) {
|
||||||
data.node?.template[name].display_name
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
unavaliableFields[data.node?.template[name].display_name!] &&
|
|
||||||
!disabled
|
|
||||||
) {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDb(true);
|
setDb(true);
|
||||||
onChange(unavaliableFields[data.node?.template[name].display_name!]);
|
onChange(unavaliableFields[data.display_name!]);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +62,7 @@ export default function InputGlobalComponent({
|
||||||
await deleteGlobalVariable(id)
|
await deleteGlobalVariable(id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
removeGlobalVariable(key);
|
removeGlobalVariable(key);
|
||||||
if (
|
if (data?.value === key && data?.load_from_db) {
|
||||||
data?.node?.template[name].value === key &&
|
|
||||||
data?.node?.template[name].load_from_db
|
|
||||||
) {
|
|
||||||
onChange("");
|
onChange("");
|
||||||
setDb(false);
|
setDb(false);
|
||||||
}
|
}
|
||||||
|
|
@ -94,8 +85,8 @@ export default function InputGlobalComponent({
|
||||||
id={"input-" + name}
|
id={"input-" + name}
|
||||||
editNode={editNode}
|
editNode={editNode}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
password={data.node?.template[name].password ?? false}
|
password={data.password ?? false}
|
||||||
value={data.node?.template[name].value ?? ""}
|
value={data.value ?? ""}
|
||||||
options={globalVariablesEntries}
|
options={globalVariablesEntries}
|
||||||
optionsPlaceholder={"Global Variables"}
|
optionsPlaceholder={"Global Variables"}
|
||||||
optionsIcon="Globe"
|
optionsIcon="Globe"
|
||||||
|
|
@ -138,10 +129,10 @@ export default function InputGlobalComponent({
|
||||||
</DeleteConfirmationModal>
|
</DeleteConfirmationModal>
|
||||||
)}
|
)}
|
||||||
selectedOption={
|
selectedOption={
|
||||||
data?.node?.template[name].load_from_db &&
|
data?.load_from_db &&
|
||||||
globalVariablesEntries &&
|
globalVariablesEntries &&
|
||||||
globalVariablesEntries.includes(data?.node?.template[name].value ?? "")
|
globalVariablesEntries.includes(data?.value ?? "")
|
||||||
? data?.node?.template[name].value
|
? data?.value
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
setSelectedOption={(value) => {
|
setSelectedOption={(value) => {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function ShadTooltip({
|
||||||
delayDuration = 500,
|
delayDuration = 500,
|
||||||
}: ShadToolTipType): JSX.Element {
|
}: ShadToolTipType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Tooltip delayDuration={delayDuration}>
|
<Tooltip defaultOpen={!children} delayDuration={delayDuration}>
|
||||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
className={cn(styleClasses, "max-w-96")}
|
className={cn(styleClasses, "max-w-96")}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { CustomCellRendererProps } from "ag-grid-react";
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
import { cn, isTimeStampString } from "../../utils/utils";
|
import { cn, isTimeStampString } from "../../../../utils/utils";
|
||||||
import ArrayReader from "../arrayReaderComponent";
|
import ArrayReader from "../../../arrayReaderComponent";
|
||||||
import DateReader from "../dateReaderComponent";
|
import DateReader from "../../../dateReaderComponent";
|
||||||
import NumberReader from "../numberReader";
|
import NumberReader from "../../../numberReader";
|
||||||
import ObjectRender from "../objectRender";
|
import ObjectRender from "../../../objectRender";
|
||||||
import StringReader from "../stringReaderComponent";
|
import StringReader from "../../../stringReaderComponent";
|
||||||
import { Badge } from "../ui/badge";
|
import { Badge } from "../../../ui/badge";
|
||||||
|
|
||||||
export default function TableAutoCellRender({
|
export default function TableAutoCellRender({
|
||||||
value,
|
value,
|
||||||
|
|
@ -43,7 +43,6 @@ export default function TableAutoCellRender({
|
||||||
} else {
|
} else {
|
||||||
return <StringReader string={value} />;
|
return <StringReader string={value} />;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "number":
|
case "number":
|
||||||
return <NumberReader number={value} />;
|
return <NumberReader number={value} />;
|
||||||
default:
|
default:
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
|
import CodeAreaComponent from "../../../codeAreaComponent";
|
||||||
|
import DictComponent from "../../../dictComponent";
|
||||||
|
import Dropdown from "../../../dropdownComponent";
|
||||||
|
import FloatComponent from "../../../floatComponent";
|
||||||
|
import InputFileComponent from "../../../inputFileComponent";
|
||||||
|
import InputGlobalComponent from "../../../inputGlobalComponent";
|
||||||
|
import InputListComponent from "../../../inputListComponent";
|
||||||
|
import IntComponent from "../../../intComponent";
|
||||||
|
import KeypairListComponent from "../../../keypairListComponent";
|
||||||
|
import PromptAreaComponent from "../../../promptComponent";
|
||||||
|
import TextAreaComponent from "../../../textAreaComponent";
|
||||||
|
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||||
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
|
import {
|
||||||
|
convertObjToArray,
|
||||||
|
convertValuesToNumbers,
|
||||||
|
hasDuplicateKeys,
|
||||||
|
scapedJSONStringfy,
|
||||||
|
} from "../../../../utils/reactflowUtils";
|
||||||
|
import { classNames } from "../../../../utils/utils";
|
||||||
|
|
||||||
|
export default function TableNodeCellRender({
|
||||||
|
node: { data },
|
||||||
|
value: {
|
||||||
|
value,
|
||||||
|
nodeClass,
|
||||||
|
handleOnNewValue: handleOnNewValueNode,
|
||||||
|
handleOnChangeDb,
|
||||||
|
},
|
||||||
|
}: CustomCellRendererProps) {
|
||||||
|
const handleOnNewValue = (newValue: any, name: string) => {
|
||||||
|
handleOnNewValueNode(newValue, name);
|
||||||
|
setTemplateData((old) => {
|
||||||
|
let newData = cloneDeep(old);
|
||||||
|
newData.value = newValue;
|
||||||
|
return newData;
|
||||||
|
});
|
||||||
|
setTemplateValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [templateValue, setTemplateValue] = useState(value);
|
||||||
|
const [templateData, setTemplateData] = useState(data);
|
||||||
|
|
||||||
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
|
||||||
|
const id = {
|
||||||
|
inputTypes: templateData.input_types,
|
||||||
|
type: templateData.type,
|
||||||
|
id: nodeClass.id,
|
||||||
|
fieldName: templateData.key,
|
||||||
|
};
|
||||||
|
const disabled =
|
||||||
|
edges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.targetHandle ===
|
||||||
|
scapedJSONStringfy(
|
||||||
|
templateData.proxy
|
||||||
|
? {
|
||||||
|
...id,
|
||||||
|
proxy: templateData.proxy,
|
||||||
|
}
|
||||||
|
: id,
|
||||||
|
),
|
||||||
|
) ?? false;
|
||||||
|
function getCellType() {
|
||||||
|
switch (templateData.type) {
|
||||||
|
case "str":
|
||||||
|
if (!templateData.options) {
|
||||||
|
return templateData?.list ? (
|
||||||
|
<InputListComponent
|
||||||
|
componentName={templateData.key ?? undefined}
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
value={
|
||||||
|
!templateValue || templateValue === "" ? [""] : templateValue
|
||||||
|
}
|
||||||
|
onChange={(value: string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : templateData.multiline ? (
|
||||||
|
<TextAreaComponent
|
||||||
|
id={"textarea-edit-" + templateData.name}
|
||||||
|
data-testid={"textarea-edit-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InputGlobalComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
onChange={(value) => handleOnNewValue(value, templateData.key)}
|
||||||
|
setDb={(value) => {
|
||||||
|
handleOnChangeDb(value, templateData.key);
|
||||||
|
}}
|
||||||
|
name={templateData.key}
|
||||||
|
data={templateData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
editNode={true}
|
||||||
|
options={templateData.options}
|
||||||
|
onSelect={(value) => handleOnNewValue(value, templateData.key)}
|
||||||
|
value={templateValue ?? "Choose an option"}
|
||||||
|
id={"dropdown-edit-" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "NestedDict":
|
||||||
|
return (
|
||||||
|
<DictComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue.toString() === "{}" ? {} : templateValue}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
handleOnNewValue(newValue, templateData.key);
|
||||||
|
}}
|
||||||
|
id="editnode-div-dict-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "dict":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"max-h-48 w-full overflow-auto custom-scroll",
|
||||||
|
templateValue?.length > 1 ? "my-3" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<KeypairListComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={
|
||||||
|
templateValue?.length === 0 || !templateValue
|
||||||
|
? [{ "": "" }]
|
||||||
|
: convertObjToArray(templateValue, templateData.type)
|
||||||
|
}
|
||||||
|
duplicateKey={errorDuplicateKey}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||||
|
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||||
|
handleOnNewValue(valueToNumbers, templateData.key);
|
||||||
|
}}
|
||||||
|
isList={templateData.list ?? false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "bool":
|
||||||
|
return (
|
||||||
|
<ToggleShadComponent
|
||||||
|
id={"toggle-edit-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
enabled={templateValue}
|
||||||
|
setEnabled={(isEnabled) => {
|
||||||
|
handleOnNewValue(isEnabled, templateData.key);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
editNode={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "float":
|
||||||
|
return (
|
||||||
|
<FloatComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
rangeSpec={templateData.rangeSpec}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "int":
|
||||||
|
return (
|
||||||
|
<IntComponent
|
||||||
|
rangeSpec={templateData.rangeSpec}
|
||||||
|
id={"edit-int-input-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "file":
|
||||||
|
return (
|
||||||
|
<InputFileComponent
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
fileTypes={templateData.fileTypes}
|
||||||
|
onFileChange={(filePath: string) => {
|
||||||
|
templateData.file_path = filePath;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "prompt":
|
||||||
|
return (
|
||||||
|
<PromptAreaComponent
|
||||||
|
readonly={nodeClass.flow ? true : false}
|
||||||
|
field_name={templateData.key}
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
nodeClass={nodeClass}
|
||||||
|
setNodeClass={(value) => {
|
||||||
|
nodeClass = value;
|
||||||
|
}}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
id={"prompt-area-edit-" + templateData.name}
|
||||||
|
data-testid={"modal-prompt-input-" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "code":
|
||||||
|
return (
|
||||||
|
<CodeAreaComponent
|
||||||
|
readonly={nodeClass.flow && templateData.dynamic ? true : false}
|
||||||
|
dynamic={templateData.dynamic ?? false}
|
||||||
|
setNodeClass={(value) => {
|
||||||
|
nodeClass = value;
|
||||||
|
}}
|
||||||
|
nodeClass={nodeClass}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
id={"code-area-edit" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "Any":
|
||||||
|
return <>-</>;
|
||||||
|
default:
|
||||||
|
return String(templateValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group flex h-full w-[300px] items-center justify-center py-2.5">
|
||||||
|
{getCellType()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||||
|
|
||||||
|
export default function TableToggleCellRender({
|
||||||
|
value: { name, enabled, setEnabled },
|
||||||
|
}: CustomCellRendererProps) {
|
||||||
|
const [value, setValue] = useState(enabled);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full items-center">
|
||||||
|
<ToggleShadComponent
|
||||||
|
id={"show" + name}
|
||||||
|
enabled={value}
|
||||||
|
setEnabled={(e) => {
|
||||||
|
setValue(e);
|
||||||
|
setEnabled(e);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
editNode={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { CustomTooltipProps } from "ag-grid-react";
|
||||||
|
|
||||||
|
export default function TableTooltipRender({ value }: CustomTooltipProps) {
|
||||||
|
return (
|
||||||
|
<div className="z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1">
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -125,6 +125,9 @@ const TableComponent = forwardRef<
|
||||||
}}
|
}}
|
||||||
columnDefs={colDef}
|
columnDefs={colDef}
|
||||||
ref={realRef}
|
ref={realRef}
|
||||||
|
getRowId={(params) => {
|
||||||
|
return params.data.id;
|
||||||
|
}}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
onGridReady={onGridReady}
|
onGridReady={onGridReady}
|
||||||
onColumnMoved={onColumnMoved}
|
onColumnMoved={onColumnMoved}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ export default function ToggleShadComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
|
|
||||||
<Switch
|
<Switch
|
||||||
id={id}
|
id={id}
|
||||||
data-testid={id}
|
data-testid={id}
|
||||||
|
|
@ -43,6 +42,5 @@ export default function ToggleShadComponent({
|
||||||
setEnabled(isEnabled);
|
setEnabled(isEnabled);
|
||||||
}}
|
}}
|
||||||
></Switch>
|
></Switch>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,7 @@ function RefreshButton({
|
||||||
|
|
||||||
// icon class name should take into account the disabled state and the loading state
|
// icon class name should take into account the disabled state and the loading state
|
||||||
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
|
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
|
||||||
const iconClassName = cn(
|
const iconClassName = cn("h-4 w-4 animate-wiggle", disabledIconTextClass);
|
||||||
"h-4 w-4",
|
|
||||||
isLoading ? "animate-spin" : "animate-wiggle",
|
|
||||||
disabledIconTextClass
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -44,10 +40,11 @@ function RefreshButton({
|
||||||
className={classNames}
|
className={classNames}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
id={id}
|
id={id}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
{button_text && <span className="mr-1">{button_text}</span>}
|
{button_text && <span className="mr-1">{button_text}</span>}
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name={isLoading ? "Loader2" : "RefreshCcw"}
|
name={"RefreshCcw"}
|
||||||
className={iconClassName}
|
className={iconClassName}
|
||||||
id={id + "-icon"}
|
id={id + "-icon"}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const USER_EDIT_ERROR_ALERT = "Error on edit user";
|
||||||
export const USER_ADD_ERROR_ALERT = "Error when adding new user";
|
export const USER_ADD_ERROR_ALERT = "Error when adding new user";
|
||||||
export const SIGNIN_ERROR_ALERT = "Error signing in";
|
export const SIGNIN_ERROR_ALERT = "Error signing in";
|
||||||
export const DEL_KEY_ERROR_ALERT = "Error on delete key";
|
export const DEL_KEY_ERROR_ALERT = "Error on delete key";
|
||||||
|
export const DEL_KEY_ERROR_ALERT_PLURAL = "Error on delete keys";
|
||||||
export const UPLOAD_ERROR_ALERT = "Error uploading file";
|
export const UPLOAD_ERROR_ALERT = "Error uploading file";
|
||||||
export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
|
export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
|
||||||
export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
|
export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
|
||||||
|
|
@ -54,6 +55,7 @@ export const USER_DEL_SUCCESS_ALERT = "Success! User deleted!";
|
||||||
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
|
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
|
||||||
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
|
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
|
||||||
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
|
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
|
||||||
|
export const DEL_KEY_SUCCESS_ALERT_PLURAL = "Success! Keys deleted!";
|
||||||
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
|
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
|
||||||
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -613,11 +613,8 @@ export const FETCH_ERROR_DESCRIPION =
|
||||||
|
|
||||||
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH_1 =
|
export const API_PAGE_PARAGRAPH =
|
||||||
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
|
"Your secret API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH_2 =
|
|
||||||
"Do not share your API key with others, or expose it in the browser or other client-side code.";
|
|
||||||
|
|
||||||
export const API_PAGE_USER_KEYS =
|
export const API_PAGE_USER_KEYS =
|
||||||
"This user does not have any keys assigned at the moment.";
|
"This user does not have any keys assigned at the moment.";
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ export default function ParameterComponent({
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
|
@ -392,7 +391,7 @@ export default function ParameterComponent({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
name={name}
|
name={name}
|
||||||
data={data}
|
data={data.node?.template[name]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{data.node?.template[name]?.refresh_button && (
|
{data.node?.template[name]?.refresh_button && (
|
||||||
|
|
@ -448,8 +447,8 @@ export default function ParameterComponent({
|
||||||
data.node?.template[name]?.real_time_refresh)
|
data.node?.template[name]?.real_time_refresh)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="mt-2 flex w-full items-center">
|
<div className="mt-2 flex w-full items-center gap-2">
|
||||||
<div className="w-5/6 flex-grow">
|
<div className="flex-1">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|
@ -467,7 +466,6 @@ export default function ParameterComponent({
|
||||||
name={name}
|
name={name}
|
||||||
data={data}
|
data={data}
|
||||||
button_text={data.node?.template[name]?.refresh_button_text}
|
button_text={data.node?.template[name]?.refresh_button_text}
|
||||||
className="extra-side-bar-buttons ml-2 mt-1"
|
|
||||||
handleUpdateValues={handleRefreshButtonPress}
|
handleUpdateValues={handleRefreshButtonPress}
|
||||||
id={"refresh-button-" + name}
|
id={"refresh-button-" + name}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
import emojiRegex from "emoji-regex";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
|
@ -220,8 +221,7 @@ export default function GenericNode({
|
||||||
|
|
||||||
const nameEditable = true;
|
const nameEditable = true;
|
||||||
|
|
||||||
const emojiRegex = /\p{Emoji}/u;
|
const isEmoji = emojiRegex().test(data?.node?.icon!);
|
||||||
const isEmoji = emojiRegex.test(data?.node?.icon!);
|
|
||||||
|
|
||||||
const iconNodeRender = useCallback(() => {
|
const iconNodeRender = useCallback(() => {
|
||||||
const iconElement = data?.node?.icon;
|
const iconElement = data?.node?.icon;
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ const ApiModal = forwardRef(
|
||||||
);
|
);
|
||||||
const pythonCode = getPythonCode(flow?.name, tweak);
|
const pythonCode = getPythonCode(flow?.name, tweak);
|
||||||
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
||||||
console.log("flow", flow);
|
|
||||||
const includeWebhook = flow.webhook;
|
const includeWebhook = flow.webhook;
|
||||||
const tweaksCode = buildTweaks(flow);
|
const tweaksCode = buildTweaks(flow);
|
||||||
const codesArray = [
|
const codesArray = [
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "x-small":
|
case "x-small":
|
||||||
minWidth = "min-w-[20vw]";
|
minWidth = "min-w-[20vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "smaller":
|
case "smaller":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
|
|
@ -12,7 +12,7 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
break;
|
break;
|
||||||
case "smaller-h-full":
|
case "smaller-h-full":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "small":
|
case "small":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
|
|
@ -20,16 +20,19 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
break;
|
break;
|
||||||
case "small-h-full":
|
case "small-h-full":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
minWidth = "min-w-[60vw]";
|
minWidth = "min-w-[60vw]";
|
||||||
height = "h-[60vh]";
|
height = "h-[60vh]";
|
||||||
break;
|
break;
|
||||||
|
case "medium-tall":
|
||||||
|
minWidth = "min-w-[60vw]";
|
||||||
|
height = "h-[90vh]";
|
||||||
|
break;
|
||||||
case "medium-h-full":
|
case "medium-h-full":
|
||||||
minWidth = "min-w-[60vw]";
|
minWidth = "min-w-[60vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "large":
|
case "large":
|
||||||
minWidth = "min-w-[85vw]";
|
minWidth = "min-w-[85vw]";
|
||||||
|
|
@ -56,11 +59,11 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
|
|
||||||
case "large-h-full":
|
case "large-h-full":
|
||||||
minWidth = "min-w-[80vw]";
|
minWidth = "min-w-[80vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
minWidth = "min-w-[80vw]";
|
minWidth = "min-w-[80vw]";
|
||||||
height = "h-[80vh]";
|
height = "h-[90vh]";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return { minWidth, height };
|
return { minWidth, height };
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { Button } from "../../components/ui/button";
|
||||||
import { modalHeaderType } from "../../types/components";
|
import { modalHeaderType } from "../../types/components";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
||||||
|
import * as Form from "@radix-ui/react-form";
|
||||||
|
|
||||||
type ContentProps = { children: ReactNode };
|
type ContentProps = { children: ReactNode };
|
||||||
type HeaderProps = { children: ReactNode; description: string };
|
type HeaderProps = { children: ReactNode; description: string };
|
||||||
|
|
@ -52,10 +53,10 @@ const Trigger: React.FC<TriggerProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
const Header: React.FC<{
|
||||||
children,
|
children: ReactNode;
|
||||||
description,
|
description: string | JSX.Element | null;
|
||||||
}: modalHeaderType): JSX.Element => {
|
}> = ({ children, description }: modalHeaderType): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
||||||
|
|
@ -111,6 +112,7 @@ interface BaseModalProps {
|
||||||
| "smaller"
|
| "smaller"
|
||||||
| "small"
|
| "small"
|
||||||
| "medium"
|
| "medium"
|
||||||
|
| "medium-tall"
|
||||||
| "large"
|
| "large"
|
||||||
| "three-cards"
|
| "three-cards"
|
||||||
| "large-thin"
|
| "large-thin"
|
||||||
|
|
@ -179,31 +181,33 @@ function BaseModal({
|
||||||
) : (
|
) : (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
{triggerChild}
|
{triggerChild}
|
||||||
<DialogContent className={cn(minWidth, "duration-300")}>
|
<DialogContent
|
||||||
|
className={cn(minWidth, height, "flex flex-col duration-300")}
|
||||||
|
>
|
||||||
<div className="truncate-doubleline word-break-break-word">
|
<div className="truncate-doubleline word-break-break-word">
|
||||||
{headerChild}
|
{headerChild}
|
||||||
</div>
|
</div>
|
||||||
{onSubmit ? (
|
{onSubmit ? (
|
||||||
<form
|
<Form.Root
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
}}
|
}}
|
||||||
className="flex flex-col gap-6"
|
className="flex flex-1 flex-col gap-6"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col ${height} w-full transition-all duration-300`}
|
className={`flex w-full flex-1 flex-col transition-all duration-300`}
|
||||||
>
|
>
|
||||||
{ContentChild}
|
{ContentChild}
|
||||||
</div>
|
</div>
|
||||||
{ContentFooter && (
|
{ContentFooter && (
|
||||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</Form.Root>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col ${height} w-full transition-all duration-300`}
|
className={`flex w-full flex-1 flex-col transition-all duration-300`}
|
||||||
>
|
>
|
||||||
{ContentChild}
|
{ContentChild}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { ColDef, ValueGetterParams } from "ag-grid-community";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import TableAutoCellRender from "../../../components/tableComponent/components/tableAutoCellRender";
|
||||||
|
import TableNodeCellRender from "../../../components/tableComponent/components/tableNodeCellRender";
|
||||||
|
import TableToggleCellRender from "../../../components/tableComponent/components/tableToggleCellRender";
|
||||||
|
import TableTooltipRender from "../../../components/tableComponent/components/tableTooltipRender";
|
||||||
|
|
||||||
|
const useColumnDefs = (
|
||||||
|
myData: any,
|
||||||
|
handleOnNewValue: (newValue: any, name: string) => void,
|
||||||
|
changeAdvanced: (n: string) => void,
|
||||||
|
open: boolean,
|
||||||
|
) => {
|
||||||
|
const columnDefs: ColDef[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
headerName: "Name",
|
||||||
|
field: "display_name",
|
||||||
|
valueGetter: (params) => {
|
||||||
|
const templateParam = params.data;
|
||||||
|
return (
|
||||||
|
(templateParam.display_name
|
||||||
|
? templateParam.display_name
|
||||||
|
: templateParam.name) ?? params.data.key
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tooltipField: "display_name",
|
||||||
|
tooltipComponent: TableTooltipRender,
|
||||||
|
wrapText: true,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Description",
|
||||||
|
field: "info",
|
||||||
|
tooltipField: "info",
|
||||||
|
tooltipComponent: TableTooltipRender,
|
||||||
|
wrapText: true,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 2,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Value",
|
||||||
|
field: "value",
|
||||||
|
cellRenderer: TableNodeCellRender,
|
||||||
|
valueGetter: (params: ValueGetterParams) => {
|
||||||
|
return {
|
||||||
|
value: params.data.value,
|
||||||
|
nodeClass: myData.node,
|
||||||
|
handleOnNewValue: handleOnNewValue,
|
||||||
|
handleOnChangeDb: (value, key) => {
|
||||||
|
myData.node!.template[key].load_from_db = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
minWidth: 330,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Show",
|
||||||
|
field: "advanced",
|
||||||
|
cellRenderer: TableToggleCellRender,
|
||||||
|
valueGetter: (params: ValueGetterParams) => {
|
||||||
|
return {
|
||||||
|
name: params.data.name,
|
||||||
|
enabled: !params.data.advanced,
|
||||||
|
setEnabled: () => {
|
||||||
|
changeAdvanced(params.data.key);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
editable: false,
|
||||||
|
maxWidth: 80,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[open, myData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columnDefs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColumnDefs;
|
||||||
37
src/frontend/src/modals/editNodeModal/hooks/use-row-data.tsx
Normal file
37
src/frontend/src/modals/editNodeModal/hooks/use-row-data.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants";
|
||||||
|
import { TemplateVariableType } from "../../../types/api";
|
||||||
|
|
||||||
|
const useRowData = (myData, open) => {
|
||||||
|
const rowData = useMemo(() => {
|
||||||
|
return Object.keys(myData.node!.template)
|
||||||
|
.filter((key: string) => {
|
||||||
|
const templateParam = myData.node!.template[
|
||||||
|
key
|
||||||
|
] as TemplateVariableType;
|
||||||
|
return (
|
||||||
|
key.charAt(0) !== "_" &&
|
||||||
|
templateParam.show &&
|
||||||
|
LANGFLOW_SUPPORTED_TYPES.has(templateParam.type) &&
|
||||||
|
!(
|
||||||
|
(key === "code" && templateParam.type === "code") ||
|
||||||
|
(key.includes("code") && templateParam.proxy)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((key: string) => {
|
||||||
|
const templateParam = myData.node!.template[
|
||||||
|
key
|
||||||
|
] as TemplateVariableType;
|
||||||
|
return {
|
||||||
|
...templateParam,
|
||||||
|
key: key,
|
||||||
|
id: key,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [open, myData]);
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useRowData;
|
||||||
|
|
@ -1,43 +1,13 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { ColDef, GridApi } from "ag-grid-community";
|
||||||
import { forwardRef, useEffect, useState } from "react";
|
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
|
||||||
import DictComponent from "../../components/dictComponent";
|
|
||||||
import Dropdown from "../../components/dropdownComponent";
|
|
||||||
import FloatComponent from "../../components/floatComponent";
|
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import InputFileComponent from "../../components/inputFileComponent";
|
import TableComponent from "../../components/tableComponent";
|
||||||
import InputGlobalComponent from "../../components/inputGlobalComponent";
|
|
||||||
import InputListComponent from "../../components/inputListComponent";
|
|
||||||
import IntComponent from "../../components/intComponent";
|
|
||||||
import KeypairListComponent from "../../components/keypairListComponent";
|
|
||||||
import PromptAreaComponent from "../../components/promptComponent";
|
|
||||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
|
||||||
import TextAreaComponent from "../../components/textAreaComponent";
|
|
||||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "../../components/ui/table";
|
|
||||||
import {
|
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
|
||||||
limitScrollFieldsModal,
|
|
||||||
} from "../../constants/constants";
|
|
||||||
import { Case } from "../../shared/components/caseComponent";
|
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import {
|
|
||||||
convertObjToArray,
|
|
||||||
convertValuesToNumbers,
|
|
||||||
hasDuplicateKeys,
|
|
||||||
scapedJSONStringfy,
|
|
||||||
} from "../../utils/reactflowUtils";
|
|
||||||
import { classNames } from "../../utils/utils";
|
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
import useColumnDefs from "./hooks/use-column-defs";
|
||||||
|
import useRowData from "./hooks/use-row-data";
|
||||||
|
|
||||||
const EditNodeModal = forwardRef(
|
const EditNodeModal = forwardRef(
|
||||||
(
|
(
|
||||||
|
|
@ -52,61 +22,51 @@ const EditNodeModal = forwardRef(
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
},
|
},
|
||||||
ref,
|
ref
|
||||||
) => {
|
) => {
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const myData = useRef(data);
|
||||||
|
|
||||||
const dataFromStore = nodes.find((node) => node.id === node.id)?.data;
|
|
||||||
|
|
||||||
const [myData, setMyData] = useState(dataFromStore ?? data);
|
|
||||||
|
|
||||||
const edges = useFlowStore((state) => state.edges);
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
function changeAdvanced(n) {
|
function changeAdvanced(n) {
|
||||||
setMyData((old) => {
|
myData.current.node!.template[n].advanced =
|
||||||
let newData = cloneDeep(old);
|
!myData.current.node!.template[n]?.advanced;
|
||||||
newData.node!.template[n].advanced =
|
|
||||||
!newData.node!.template[n].advanced;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnNewValue = (newValue: any, name) => {
|
const handleOnNewValue = (newValue: any, name) => {
|
||||||
setMyData((old) => {
|
myData.current.node!.template[name].value = newValue;
|
||||||
let newData = cloneDeep(old);
|
|
||||||
newData.node!.template[name].value = newValue;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rowData = useRowData(data, open);
|
||||||
|
|
||||||
|
const columnDefs: ColDef[] = useColumnDefs(
|
||||||
|
data,
|
||||||
|
handleOnNewValue,
|
||||||
|
changeAdvanced,
|
||||||
|
open
|
||||||
|
);
|
||||||
|
|
||||||
|
const [gridApi, setGridApi] = useState<GridApi | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (gridApi && open) {
|
||||||
setMyData(data); // reset data to what it is on node when opening modal
|
myData.current = data;
|
||||||
|
gridApi.refreshCells();
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [gridApi, open]);
|
||||||
|
|
||||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
|
||||||
|
|
||||||
const type = (templateParam) => {
|
|
||||||
return myData.node?.template[templateParam].type;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal
|
<BaseModal
|
||||||
key={data.id}
|
key={data.id}
|
||||||
size="large-h-full"
|
size="medium-tall"
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
onChangeOpenModal={(open) => {
|
|
||||||
setMyData(data);
|
|
||||||
}}
|
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
setNode(data.id, (old) => ({
|
setNode(data.id, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
data: {
|
data: {
|
||||||
...old.data,
|
...old.data,
|
||||||
node: myData.node,
|
node: myData.current.node,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
@ -115,11 +75,12 @@ const EditNodeModal = forwardRef(
|
||||||
<BaseModal.Trigger>
|
<BaseModal.Trigger>
|
||||||
<></>
|
<></>
|
||||||
</BaseModal.Trigger>
|
</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={myData.node?.description!}>
|
<BaseModal.Header description={data.node?.description!}>
|
||||||
<span className="pr-2">{myData.type}</span>
|
<span className="pr-2">{data.type}</span>
|
||||||
<Badge variant="secondary">ID: {myData.id}</Badge>
|
<Badge variant="secondary">ID: {data.id}</Badge>
|
||||||
</BaseModal.Header>
|
</BaseModal.Header>
|
||||||
<BaseModal.Content>
|
<BaseModal.Content>
|
||||||
|
<div className="flex h-full flex-col">
|
||||||
<div className="flex pb-2">
|
<div className="flex pb-2">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Variable"
|
name="Variable"
|
||||||
|
|
@ -128,513 +89,16 @@ const EditNodeModal = forwardRef(
|
||||||
<span className="edit-node-modal-span">Parameters</span>
|
<span className="edit-node-modal-span">Parameters</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="edit-node-modal-arrangement">
|
<div className="h-full">
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"edit-node-modal-box",
|
|
||||||
nodeLength > limitScrollFieldsModal
|
|
||||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{nodeLength > 0 && (
|
{nodeLength > 0 && (
|
||||||
<div className="edit-node-modal-table">
|
<TableComponent
|
||||||
<Table className="table-fixed bg-muted outline-1">
|
onGridReady={(params) => {
|
||||||
<TableHeader className="edit-node-modal-table-header">
|
setGridApi(params.api);
|
||||||
<TableRow className="">
|
|
||||||
<TableHead className="h-7 text-center">PARAM</TableHead>
|
|
||||||
<TableHead className="h-7 text-center">DESC</TableHead>
|
|
||||||
<TableHead className="h-7 p-0 text-center">
|
|
||||||
VALUE
|
|
||||||
</TableHead>
|
|
||||||
<TableHead className="h-7 text-center">SHOW</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody className="p-0">
|
|
||||||
{Object.keys(myData.node!.template)
|
|
||||||
.filter(
|
|
||||||
(templateParam) =>
|
|
||||||
templateParam.charAt(0) !== "_" &&
|
|
||||||
myData.node?.template[templateParam].show &&
|
|
||||||
LANGFLOW_SUPPORTED_TYPES.has(
|
|
||||||
myData.node!.template[templateParam].type,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((templateParam, index) => {
|
|
||||||
let id = {
|
|
||||||
inputTypes:
|
|
||||||
myData.node!.template[templateParam].input_types,
|
|
||||||
type: myData.node!.template[templateParam].type,
|
|
||||||
id: myData.id,
|
|
||||||
fieldName: templateParam,
|
|
||||||
};
|
|
||||||
let disabled =
|
|
||||||
edges.some(
|
|
||||||
(edge) =>
|
|
||||||
edge.targetHandle ===
|
|
||||||
scapedJSONStringfy(
|
|
||||||
myData.node!.template[templateParam].proxy
|
|
||||||
? {
|
|
||||||
...id,
|
|
||||||
proxy:
|
|
||||||
myData.node?.template[templateParam]
|
|
||||||
.proxy,
|
|
||||||
}
|
|
||||||
: id,
|
|
||||||
),
|
|
||||||
) ?? false;
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={index}
|
|
||||||
className={
|
|
||||||
"h-10 " +
|
|
||||||
((templateParam === "code" &&
|
|
||||||
type(templateParam) === "code") ||
|
|
||||||
(templateParam.includes("code") &&
|
|
||||||
myData.node?.template[templateParam].proxy)
|
|
||||||
? " hidden "
|
|
||||||
: "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
|
||||||
<ShadTooltip
|
|
||||||
styleClasses="z-50"
|
|
||||||
content={
|
|
||||||
myData.node?.template[templateParam].proxy
|
|
||||||
? myData.node?.template[templateParam]
|
|
||||||
.proxy?.id
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
? myData.node!.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{myData.node?.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
? myData.node!.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.name}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
|
||||||
<ShadTooltip
|
|
||||||
styleClasses="z-50"
|
|
||||||
content={
|
|
||||||
data.node?.template[templateParam]?.info ??
|
|
||||||
null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{data.node?.template[templateParam]?.info ??
|
|
||||||
""}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "str" &&
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.options
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
{myData.node!.template[templateParam]
|
|
||||||
?.list ? (
|
|
||||||
<InputListComponent
|
|
||||||
componentName={templateParam}
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
value={
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.value ||
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value === ""
|
|
||||||
? [""]
|
|
||||||
: myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value
|
|
||||||
}
|
|
||||||
onChange={(value: string[]) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
value,
|
|
||||||
templateParam,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
tooltipShowDelay={0.5}
|
||||||
|
columnDefs={columnDefs}
|
||||||
|
rowData={rowData}
|
||||||
/>
|
/>
|
||||||
) : myData.node!.template[templateParam]
|
|
||||||
.multiline ? (
|
|
||||||
<TextAreaComponent
|
|
||||||
id={
|
|
||||||
"textarea-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
data-testid={
|
|
||||||
"textarea-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(
|
|
||||||
value: string | string[],
|
|
||||||
) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
value,
|
|
||||||
templateParam,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InputGlobalComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
onChange={(value) =>
|
|
||||||
handleOnNewValue(value, templateParam)
|
|
||||||
}
|
|
||||||
setDb={(value) => {
|
|
||||||
setMyData((oldData) => {
|
|
||||||
let newData = cloneDeep(oldData);
|
|
||||||
newData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].load_from_db = value;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
name={templateParam}
|
|
||||||
data={myData}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "NestedDict"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className=" w-full">
|
|
||||||
<DictComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
]?.value?.toString() === "{}"
|
|
||||||
? {}
|
|
||||||
: myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value = newValue;
|
|
||||||
handleOnNewValue(
|
|
||||||
newValue,
|
|
||||||
templateParam,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
id="editnode-div-dict-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "dict"}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"max-h-48 w-full overflow-auto custom-scroll",
|
|
||||||
myData.node!.template[templateParam].value
|
|
||||||
?.length > 1
|
|
||||||
? "my-3"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<KeypairListComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value?.length === 0 ||
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
? [{ "": "" }]
|
|
||||||
: convertObjToArray(
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value,
|
|
||||||
type(templateParam)!,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
duplicateKey={errorDuplicateKey}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
const valueToNumbers =
|
|
||||||
convertValuesToNumbers(newValue);
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value = valueToNumbers;
|
|
||||||
setErrorDuplicateKey(
|
|
||||||
hasDuplicateKeys(valueToNumbers),
|
|
||||||
);
|
|
||||||
handleOnNewValue(
|
|
||||||
valueToNumbers,
|
|
||||||
templateParam,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isList={
|
|
||||||
data.node?.template[templateParam]
|
|
||||||
?.list ?? false
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "bool"}
|
|
||||||
>
|
|
||||||
<div className="ml-auto">
|
|
||||||
{" "}
|
|
||||||
<ToggleShadComponent
|
|
||||||
id={
|
|
||||||
"toggle-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
enabled={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
}
|
|
||||||
setEnabled={(isEnabled) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
isEnabled,
|
|
||||||
templateParam,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
editNode={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "float"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<FloatComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
rangeSpec={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.rangeSpec
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "str" &&
|
|
||||||
myData.node!.template[templateParam].options
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<Dropdown
|
|
||||||
editNode={true}
|
|
||||||
options={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.options
|
|
||||||
}
|
|
||||||
onSelect={(value) =>
|
|
||||||
handleOnNewValue(value, templateParam)
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? "Choose an option"
|
|
||||||
}
|
|
||||||
id={
|
|
||||||
"dropdown-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
></Dropdown>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case condition={type(templateParam) === "int"}>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<IntComponent
|
|
||||||
rangeSpec={
|
|
||||||
data.node?.template[templateParam]
|
|
||||||
?.rangeSpec
|
|
||||||
}
|
|
||||||
id={
|
|
||||||
"edit-int-input-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "file"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<InputFileComponent
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
fileTypes={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.fileTypes
|
|
||||||
}
|
|
||||||
onFileChange={(filePath: string) => {
|
|
||||||
data.node!.template[
|
|
||||||
templateParam
|
|
||||||
].file_path = filePath;
|
|
||||||
}}
|
|
||||||
></InputFileComponent>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "prompt"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<PromptAreaComponent
|
|
||||||
readonly={
|
|
||||||
myData.node?.flow ? true : false
|
|
||||||
}
|
|
||||||
field_name={templateParam}
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
nodeClass={myData.node}
|
|
||||||
setNodeClass={(nodeClass) => {
|
|
||||||
myData.node = nodeClass;
|
|
||||||
}}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
id={
|
|
||||||
"prompt-area-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
data-testid={
|
|
||||||
"modal-prompt-input-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "code"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<CodeAreaComponent
|
|
||||||
readonly={
|
|
||||||
myData.node?.flow &&
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.dynamic
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
dynamic={
|
|
||||||
data.node!.template[templateParam]
|
|
||||||
?.dynamic ?? false
|
|
||||||
}
|
|
||||||
setNodeClass={(nodeClass) => {
|
|
||||||
data.node = nodeClass;
|
|
||||||
}}
|
|
||||||
nodeClass={data.node}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
id={
|
|
||||||
"code-area-edit" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case condition={type(templateParam) === "Any"}>
|
|
||||||
<>-</>
|
|
||||||
</Case>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="p-0 text-right">
|
|
||||||
<div className="items-center text-center">
|
|
||||||
<ToggleShadComponent
|
|
||||||
id={
|
|
||||||
"show" +
|
|
||||||
myData.node?.template[templateParam].name
|
|
||||||
}
|
|
||||||
enabled={
|
|
||||||
!myData.node?.template[templateParam]
|
|
||||||
.advanced
|
|
||||||
}
|
|
||||||
setEnabled={(e) => {
|
|
||||||
changeAdvanced(templateParam);
|
|
||||||
}}
|
|
||||||
disabled={disabled}
|
|
||||||
size="small"
|
|
||||||
editNode={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -643,7 +107,7 @@ const EditNodeModal = forwardRef(
|
||||||
<BaseModal.Footer submit={{ label: "Save Changes" }} />
|
<BaseModal.Footer submit={{ label: "Save Changes" }} />
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default EditNodeModal;
|
export default EditNodeModal;
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,10 @@ import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
export default function SecretKeyModal({
|
export default function SecretKeyModal({
|
||||||
title,
|
|
||||||
cancelText,
|
|
||||||
confirmationText,
|
|
||||||
children,
|
children,
|
||||||
icon,
|
|
||||||
data,
|
data,
|
||||||
onCloseModal,
|
onCloseModal,
|
||||||
}: ApiKeyType) {
|
}: ApiKeyType) {
|
||||||
const Icon: any = nodeIconsLucide[icon];
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||||
|
|
@ -66,38 +61,60 @@ export default function SecretKeyModal({
|
||||||
.catch((err) => {});
|
.catch((err) => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSubmitForm() {
|
||||||
|
if (!renderKey) {
|
||||||
|
setRenderKey(true);
|
||||||
|
handleAddNewKey();
|
||||||
|
} else {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
|
<BaseModal
|
||||||
|
onSubmit={handleSubmitForm}
|
||||||
|
size="small-h-full"
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={""}>
|
<BaseModal.Header
|
||||||
<span className="pr-2">{title}</span>
|
description={
|
||||||
<Icon
|
renderKey ? (
|
||||||
name="icon"
|
|
||||||
className="h-6 w-6 pl-1 text-foreground"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</BaseModal.Header>
|
|
||||||
<BaseModal.Content>
|
|
||||||
{renderKey === true && (
|
|
||||||
<>
|
<>
|
||||||
<span className="text-xs">
|
{" "}
|
||||||
Please save this secret key somewhere safe and accessible. For
|
Please save this secret key somewhere safe and accessible. For
|
||||||
security reasons,{" "}
|
security reasons,{" "}
|
||||||
<strong>you won't be able to view it again</strong> through your
|
<strong>you won't be able to view it again</strong> through your
|
||||||
account. If you lose this secret key, you'll need to generate a
|
account. If you lose this secret key, you'll need to generate a
|
||||||
new one.
|
new one.
|
||||||
</span>
|
</>
|
||||||
<div className="flex pt-3">
|
) : (
|
||||||
|
<>Create a secret API Key to use Langflow API.</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="pr-2">Create API Key</span>
|
||||||
|
<IconComponent
|
||||||
|
name="Key"
|
||||||
|
className="h-6 w-6 pl-1 text-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</BaseModal.Header>
|
||||||
|
<BaseModal.Content>
|
||||||
|
{renderKey ? (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Input ref={inputRef} readOnly={true} value={apiKeyValue} />
|
<Input ref={inputRef} readOnly={true} value={apiKeyValue} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<Button
|
<Button
|
||||||
className="ml-3"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCopyClick();
|
handleCopyClick();
|
||||||
}}
|
}}
|
||||||
|
variant="none"
|
||||||
|
size="none"
|
||||||
>
|
>
|
||||||
{textCopied ? (
|
{textCopied ? (
|
||||||
<IconComponent name="Copy" className="h-4 w-4" />
|
<IconComponent name="Copy" className="h-4 w-4" />
|
||||||
|
|
@ -106,78 +123,29 @@ export default function SecretKeyModal({
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
) : (
|
||||||
|
<Form.Field name="apikey">
|
||||||
<Form.Root
|
<div className="flex items-center justify-between gap-2">
|
||||||
onSubmit={(event) => {
|
|
||||||
setRenderKey(true);
|
|
||||||
handleAddNewKey();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderKey === false && (
|
|
||||||
<div className="grid gap-5">
|
|
||||||
<Form.Field name="username">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "baseline",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form.Label className="data-[invalid]:label-invalid">
|
|
||||||
Name (optional){" "}
|
|
||||||
</Form.Label>
|
|
||||||
</div>
|
|
||||||
<Form.Control asChild>
|
<Form.Control asChild>
|
||||||
<input
|
<Input
|
||||||
|
//fake api key
|
||||||
|
id="primary-input"
|
||||||
|
value={apiKeyName}
|
||||||
|
ref={inputRef}
|
||||||
onChange={({ target: { value } }) => {
|
onChange={({ target: { value } }) => {
|
||||||
setApiKeyName(value);
|
setApiKeyName(value);
|
||||||
}}
|
}}
|
||||||
value={apiKeyName}
|
placeholder="Insert a name for your API Key"
|
||||||
className="primary-input"
|
|
||||||
placeholder="My key name"
|
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
</div>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{renderKey === false && (
|
|
||||||
<div className="float-right">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
className="mr-3"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cancelText}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Form.Submit asChild>
|
|
||||||
<Button className="mt-8">{confirmationText}</Button>
|
|
||||||
</Form.Submit>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderKey === true && (
|
|
||||||
<div className="float-right">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
setRenderKey(false);
|
|
||||||
}}
|
|
||||||
className="mt-8"
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Form.Root>
|
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
|
<BaseModal.Footer
|
||||||
|
submit={{ label: renderKey ? "Done" : "Create Secret Key" }}
|
||||||
|
/>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
|
||||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
|
||||||
import { Button } from "../../components/ui/button";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "../../components/ui/table";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
|
||||||
import { deleteApiKey, getApiKey } from "../../controllers/API";
|
|
||||||
import ConfirmationModal from "../../modals/confirmationModal";
|
|
||||||
import SecretKeyModal from "../../modals/secretKeyModal";
|
|
||||||
|
|
||||||
import moment from "moment";
|
|
||||||
import Header from "../../components/headerComponent";
|
|
||||||
import {
|
|
||||||
DEL_KEY_ERROR_ALERT,
|
|
||||||
DEL_KEY_SUCCESS_ALERT,
|
|
||||||
} from "../../constants/alerts_constants";
|
|
||||||
import {
|
|
||||||
API_PAGE_PARAGRAPH_1,
|
|
||||||
API_PAGE_PARAGRAPH_2,
|
|
||||||
API_PAGE_USER_KEYS,
|
|
||||||
LAST_USED_SPAN_1,
|
|
||||||
LAST_USED_SPAN_2,
|
|
||||||
} from "../../constants/constants";
|
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import { ApiKey } from "../../types/components";
|
|
||||||
|
|
||||||
export default function ApiKeysPage() {
|
|
||||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
|
||||||
const { userData } = useContext(AuthContext);
|
|
||||||
const [userId, setUserId] = useState("");
|
|
||||||
const keysList = useRef([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getKeys();
|
|
||||||
}, [userData]);
|
|
||||||
|
|
||||||
function getKeys() {
|
|
||||||
setLoadingKeys(true);
|
|
||||||
if (userData) {
|
|
||||||
getApiKey()
|
|
||||||
.then((keys: [ApiKey]) => {
|
|
||||||
keysList.current = keys["api_keys"];
|
|
||||||
setUserId(keys["user_id"]);
|
|
||||||
setLoadingKeys(false);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setLoadingKeys(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetFilter() {
|
|
||||||
getKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDeleteKey(keys) {
|
|
||||||
deleteApiKey(keys)
|
|
||||||
.then((res) => {
|
|
||||||
resetFilter();
|
|
||||||
setSuccessData({
|
|
||||||
title: DEL_KEY_SUCCESS_ALERT,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setErrorData({
|
|
||||||
title: DEL_KEY_ERROR_ALERT,
|
|
||||||
list: [error["response"]["data"]["detail"]],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastUsedMessage() {
|
|
||||||
return (
|
|
||||||
<div className="text-xs">
|
|
||||||
<span>
|
|
||||||
{LAST_USED_SPAN_1}
|
|
||||||
<br></br> {LAST_USED_SPAN_2}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header></Header>
|
|
||||||
{userData && (
|
|
||||||
<div className="main-page-panel">
|
|
||||||
<div className="m-auto flex h-full flex-row justify-center">
|
|
||||||
<div className="basis-5/6">
|
|
||||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
|
||||||
<div className="flex items-center justify-between space-y-2">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold tracking-tight">
|
|
||||||
API keys
|
|
||||||
</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
{API_PAGE_PARAGRAPH_1}
|
|
||||||
<br />
|
|
||||||
{API_PAGE_PARAGRAPH_2}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{keysList.current &&
|
|
||||||
keysList.current.length === 0 &&
|
|
||||||
!loadingKeys && (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2>{API_PAGE_USER_KEYS}</h2>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<>
|
|
||||||
{loadingKeys && (
|
|
||||||
<div>
|
|
||||||
<strong>Loading...</strong>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"max-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
|
||||||
(loadingKeys ? " border-0" : "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{keysList.current &&
|
|
||||||
keysList.current.length > 0 &&
|
|
||||||
!loadingKeys && (
|
|
||||||
<Table className={"table-fixed bg-muted outline-1"}>
|
|
||||||
<TableHeader
|
|
||||||
className={
|
|
||||||
loadingKeys
|
|
||||||
? "hidden"
|
|
||||||
: "table-fixed bg-muted outline-1"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="h-10">Name</TableHead>
|
|
||||||
<TableHead className="h-10">Key</TableHead>
|
|
||||||
<TableHead className="h-10">Created</TableHead>
|
|
||||||
<TableHead className="flex h-10 items-center">
|
|
||||||
Last Used
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={lastUsedMessage()}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<IconComponent
|
|
||||||
name="Info"
|
|
||||||
className="ml-1 h-3 w-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableHead>
|
|
||||||
<TableHead className="h-10">Total Uses</TableHead>
|
|
||||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
{!loadingKeys && (
|
|
||||||
<TableBody>
|
|
||||||
{keysList.current.map(
|
|
||||||
(api_keys: ApiKey, index: number) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<ShadTooltip content={api_keys.name}>
|
|
||||||
<span className="cursor-default">
|
|
||||||
{api_keys.name ? api_keys.name : "-"}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<span className="cursor-default">
|
|
||||||
{api_keys.api_key}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2 ">
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={moment(
|
|
||||||
api_keys.created_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{moment(api_keys.created_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={
|
|
||||||
moment(api_keys.last_used_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
) === "Invalid date"
|
|
||||||
? "Never"
|
|
||||||
: moment(
|
|
||||||
api_keys.last_used_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{moment(api_keys.last_used_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
) === "Invalid date"
|
|
||||||
? "Never"
|
|
||||||
: moment(
|
|
||||||
api_keys.last_used_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")}
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
{api_keys.total_uses}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="flex w-[100px] py-2 text-right">
|
|
||||||
<div className="flex">
|
|
||||||
<ConfirmationModal
|
|
||||||
title="Delete"
|
|
||||||
titleHeader="Delete User"
|
|
||||||
modalContentTitle="Attention!"
|
|
||||||
cancelText="Cancel"
|
|
||||||
confirmationText="Delete"
|
|
||||||
icon={"UserMinus2"}
|
|
||||||
data={api_keys.id}
|
|
||||||
index={index}
|
|
||||||
onConfirm={(index, keys) => {
|
|
||||||
handleDeleteKey(keys);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ConfirmationModal.Content>
|
|
||||||
<span>
|
|
||||||
Are you sure you want to delete
|
|
||||||
this key? This action cannot be
|
|
||||||
undone.
|
|
||||||
</span>
|
|
||||||
</ConfirmationModal.Content>
|
|
||||||
<ConfirmationModal.Trigger>
|
|
||||||
<IconComponent
|
|
||||||
name="Trash2"
|
|
||||||
className="ml-2 h-4 w-4 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</ConfirmationModal.Trigger>
|
|
||||||
</ConfirmationModal>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
)}
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<SecretKeyModal
|
|
||||||
title="Create new secret key"
|
|
||||||
cancelText="Cancel"
|
|
||||||
confirmationText="Create secret key"
|
|
||||||
icon={"Key"}
|
|
||||||
data={userId}
|
|
||||||
onCloseModal={getKeys}
|
|
||||||
>
|
|
||||||
<Button>
|
|
||||||
<IconComponent name="Plus" className="mr-1 h-5 w-5" />
|
|
||||||
Create new secret key
|
|
||||||
</Button>
|
|
||||||
</SecretKeyModal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -25,7 +25,6 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Global Variables",
|
title: "Global Variables",
|
||||||
href: "/settings/global-variables",
|
href: "/settings/global-variables",
|
||||||
|
|
@ -36,6 +35,16 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "API Keys",
|
||||||
|
href: "/settings/api-keys",
|
||||||
|
icon: (
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Key"
|
||||||
|
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Shortcuts",
|
title: "Shortcuts",
|
||||||
href: "/settings/shortcuts",
|
href: "/settings/shortcuts",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import ForwardedIconComponent from "../../../../../../components/genericIconComponent";
|
||||||
|
import { Button } from "../../../../../../components/ui/button";
|
||||||
|
import { API_PAGE_PARAGRAPH } from "../../../../../../constants/constants";
|
||||||
|
import SecretKeyModal from "../../../../../../modals/secretKeyModal";
|
||||||
|
import { cn } from "../../../../../../utils/utils";
|
||||||
|
|
||||||
|
type ApiKeyHeaderComponentProps = {
|
||||||
|
selectedRows: string[];
|
||||||
|
handleDeleteKey: () => void;
|
||||||
|
fetchApiKeys: () => void;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
const ApiKeyHeaderComponent = ({
|
||||||
|
selectedRows,
|
||||||
|
handleDeleteKey,
|
||||||
|
fetchApiKeys,
|
||||||
|
userId,
|
||||||
|
}: ApiKeyHeaderComponentProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||||
|
API Keys
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Key"
|
||||||
|
className="ml-2 h-5 w-5 text-primary"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">{API_PAGE_PARAGRAPH}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
|
<Button
|
||||||
|
data-testid="api-key-button-store"
|
||||||
|
variant="primary"
|
||||||
|
className="group px-2"
|
||||||
|
disabled={selectedRows.length === 0}
|
||||||
|
onClick={handleDeleteKey}
|
||||||
|
>
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Trash2"
|
||||||
|
className={cn(
|
||||||
|
"h-5 w-5 text-destructive group-disabled:text-primary",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<SecretKeyModal data={userId} onCloseModal={fetchApiKeys}>
|
||||||
|
<Button data-testid="api-key-button-store" variant="primary">
|
||||||
|
<ForwardedIconComponent name="Plus" className="mr-2 w-4" />
|
||||||
|
Add New
|
||||||
|
</Button>
|
||||||
|
</SecretKeyModal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ApiKeyHeaderComponent;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import TableAutoCellRender from "../../../../../components/tableComponent/components/tableAutoCellRender";
|
||||||
|
|
||||||
|
export const getColumnDefs = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
headerCheckboxSelection: true,
|
||||||
|
checkboxSelection: true,
|
||||||
|
showDisabledCheckboxes: true,
|
||||||
|
headerName: "Name",
|
||||||
|
field: "name",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Key",
|
||||||
|
field: "api_key",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Created",
|
||||||
|
field: "created_at",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Last Used",
|
||||||
|
field: "last_used_at",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Total Uses",
|
||||||
|
field: "total_uses",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { getApiKey } from "../../../../../controllers/API";
|
||||||
|
|
||||||
|
const useApiKeys = (userData, setLoadingKeys, keysList, setUserId) => {
|
||||||
|
const fetchApiKeys = () => {
|
||||||
|
setLoadingKeys(true);
|
||||||
|
getApiKey()
|
||||||
|
.then((keys) => {
|
||||||
|
keysList.current = keys["api_keys"].map((apikey) => ({
|
||||||
|
...apikey,
|
||||||
|
name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
|
||||||
|
last_used_at: apikey.last_used_at ?? "Never",
|
||||||
|
}));
|
||||||
|
setUserId(keys["user_id"]);
|
||||||
|
setLoadingKeys(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoadingKeys(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchApiKeys,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useApiKeys;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {
|
||||||
|
DEL_KEY_ERROR_ALERT,
|
||||||
|
DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
DEL_KEY_SUCCESS_ALERT,
|
||||||
|
DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
} from "../../../../../constants/alerts_constants";
|
||||||
|
import { deleteApiKey } from "../../../../../controllers/API";
|
||||||
|
|
||||||
|
const useDeleteApiKeys = (
|
||||||
|
selectedRows,
|
||||||
|
resetFilter,
|
||||||
|
setSuccessData,
|
||||||
|
setErrorData,
|
||||||
|
) => {
|
||||||
|
const handleDeleteKey = () => {
|
||||||
|
Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow)))
|
||||||
|
.then(() => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_SUCCESS_ALERT
|
||||||
|
: DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_ERROR_ALERT
|
||||||
|
: DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
list: [error?.response?.data?.detail],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleDeleteKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeleteApiKeys;
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { SelectionChangedEvent } from "ag-grid-community";
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import TableComponent from "../../../../components/tableComponent";
|
||||||
|
import { Card, CardContent } from "../../../../components/ui/card";
|
||||||
|
import { AuthContext } from "../../../../contexts/authContext";
|
||||||
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
|
import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
|
||||||
|
import { getColumnDefs } from "./helpers/column-defs";
|
||||||
|
import useApiKeys from "./hooks/use-api-keys";
|
||||||
|
import useDeleteApiKeys from "./hooks/use-handle-delete-key";
|
||||||
|
|
||||||
|
export default function ApiKeysPage() {
|
||||||
|
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const { userData } = useContext(AuthContext);
|
||||||
|
const [userId, setUserId] = useState("");
|
||||||
|
const keysList = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchApiKeys();
|
||||||
|
}, [userData]);
|
||||||
|
|
||||||
|
const { fetchApiKeys } = useApiKeys(
|
||||||
|
userData,
|
||||||
|
setLoadingKeys,
|
||||||
|
keysList,
|
||||||
|
setUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetFilter() {
|
||||||
|
fetchApiKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { handleDeleteKey } = useDeleteApiKeys(
|
||||||
|
selectedRows,
|
||||||
|
resetFilter,
|
||||||
|
setSuccessData,
|
||||||
|
setErrorData,
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnDefs = getColumnDefs();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between gap-6">
|
||||||
|
<ApiKeyHeaderComponent
|
||||||
|
selectedRows={selectedRows}
|
||||||
|
handleDeleteKey={handleDeleteKey}
|
||||||
|
fetchApiKeys={fetchApiKeys}
|
||||||
|
userId={userId}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
|
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||||
|
<CardContent className="h-full">
|
||||||
|
<TableComponent
|
||||||
|
overlayNoRowsTemplate="No data available"
|
||||||
|
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||||
|
setSelectedRows(
|
||||||
|
event.api.getSelectedRows().map((row) => row.id),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
rowSelection="multiple"
|
||||||
|
suppressRowClickSelection={true}
|
||||||
|
pagination={true}
|
||||||
|
columnDefs={columnDefs}
|
||||||
|
rowData={keysList.current}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,9 @@ import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
|
||||||
|
|
||||||
const AdminPage = lazy(() => import("./pages/AdminPage"));
|
const AdminPage = lazy(() => import("./pages/AdminPage"));
|
||||||
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
||||||
const ApiKeysPage = lazy(() => import("./pages/ApiKeysPage"));
|
const ApiKeysPage = lazy(
|
||||||
|
() => import("./pages/SettingsPage/pages/ApiKeysPage"),
|
||||||
|
);
|
||||||
const DeleteAccountPage = lazy(() => import("./pages/DeleteAccountPage"));
|
const DeleteAccountPage = lazy(() => import("./pages/DeleteAccountPage"));
|
||||||
const FlowPage = lazy(() => import("./pages/FlowPage"));
|
const FlowPage = lazy(() => import("./pages/FlowPage"));
|
||||||
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
||||||
|
|
@ -77,6 +79,7 @@ const Router = () => {
|
||||||
>
|
>
|
||||||
<Route index element={<Navigate replace to={"general"} />} />
|
<Route index element={<Navigate replace to={"general"} />} />
|
||||||
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
||||||
|
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||||
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
||||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||||
<Route path="messages" element={<MessagesPage />} />
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
|
|
@ -189,14 +192,6 @@ const Router = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
></Route>
|
></Route>
|
||||||
<Route
|
|
||||||
path="api-keys"
|
|
||||||
element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<ApiKeysPage />
|
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
|
||||||
></Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
||||||
|
|
@ -100,3 +100,13 @@ select:-webkit-autofill:focus {
|
||||||
.json-view-dark {
|
.json-view-dark {
|
||||||
background-color: #141924 !important;
|
background-color: #141924 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-row .ag-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ag-cell {
|
||||||
|
line-height: 1.25rem;
|
||||||
|
padding-top: 0.675rem;
|
||||||
|
padding-bottom: 0.675rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export type InputComponentType = {
|
||||||
export type ToggleComponentType = {
|
export type ToggleComponentType = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
setEnabled: (state: boolean) => void;
|
setEnabled: (state: boolean) => void;
|
||||||
disabled: boolean | undefined;
|
disabled?: boolean | undefined;
|
||||||
size: "small" | "medium" | "large";
|
size: "small" | "medium" | "large";
|
||||||
id?: string;
|
id?: string;
|
||||||
editNode?: boolean;
|
editNode?: boolean;
|
||||||
|
|
@ -393,11 +393,7 @@ export type UserInputType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiKeyType = {
|
export type ApiKeyType = {
|
||||||
title: string;
|
|
||||||
cancelText: string;
|
|
||||||
confirmationText: string;
|
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
icon: string;
|
|
||||||
data?: any;
|
data?: any;
|
||||||
onCloseModal: () => void;
|
onCloseModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
@ -547,7 +543,7 @@ export type iconsType = {
|
||||||
|
|
||||||
export type modalHeaderType = {
|
export type modalHeaderType = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
description: string | null;
|
description: string | JSX.Element | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type codeAreaModalPropsType = {
|
export type codeAreaModalPropsType = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||||
import clsx, { ClassValue } from "clsx";
|
import clsx, { ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import TableAutoCellRender from "../components/tableAutoCellRender";
|
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
||||||
import { APIDataType, TemplateVariableType } from "../types/api";
|
import { APIDataType, TemplateVariableType } from "../types/api";
|
||||||
import {
|
import {
|
||||||
groupedObjType,
|
groupedObjType,
|
||||||
|
|
@ -345,8 +345,10 @@ export function freezeObject(obj: any) {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
export function isTimeStampString(str: string): boolean {
|
export function isTimeStampString(str: string): boolean {
|
||||||
const timestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
const timestampRegexA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
||||||
return timestampRegex.test(str);
|
const timestampRegexB = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?$/;
|
||||||
|
|
||||||
|
return timestampRegexA.test(str) || timestampRegexB.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractColumnsFromRows(
|
export function extractColumnsFromRows(
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ test("dropDownComponent", async ({ page }) => {
|
||||||
expect(false).toBeTruthy();
|
expect(false).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
await page.getByTestId("more-options-modal").click();
|
await page.getByTestId("more-options-modal").click();
|
||||||
await page.getByTestId("edit-button-modal").click();
|
await page.getByTestId("edit-button-modal").click();
|
||||||
|
|
||||||
|
|
|
||||||
99
src/frontend/tests/end-to-end/generalBugs.spec.ts
Normal file
99
src/frontend/tests/end-to-end/generalBugs.spec.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("should interact with api request", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
let modalCount = 0;
|
||||||
|
try {
|
||||||
|
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||||
|
if (modalTitleElement) {
|
||||||
|
modalCount = await modalTitleElement.count();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
modalCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (modalCount === 0) {
|
||||||
|
await page.getByText("New Project", { exact: true }).click();
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
modalCount = await page.getByTestId("modal-title")?.count();
|
||||||
|
}
|
||||||
|
await page.getByTestId("blank-flow").click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.getByTestId("extended-disclosure").click();
|
||||||
|
await page.getByPlaceholder("Search").click();
|
||||||
|
await page.getByPlaceholder("Search").fill("api request");
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("dataAPI Request")
|
||||||
|
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||||
|
await page.mouse.up();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.getByTitle("fit view").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("erase button should clear the chat messages", async ({ page }) => {
|
||||||
|
if (!process.env.CI) {
|
||||||
|
dotenv.config();
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
let modalCount = 0;
|
||||||
|
try {
|
||||||
|
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||||
|
if (modalTitleElement) {
|
||||||
|
modalCount = await modalTitleElement.count();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
modalCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (modalCount === 0) {
|
||||||
|
await page.getByText("New Project", { exact: true }).click();
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
modalCount = await page.getByTestId("modal-title")?.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
await page.getByTitle("fit view").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
|
||||||
|
if (!process.env.OPENAI_API_KEY) {
|
||||||
|
//You must set the OPENAI_API_KEY on .env file to run this test
|
||||||
|
expect(false).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("popover-anchor-input-openai_api_key")
|
||||||
|
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||||
|
await page.getByText("Playground", { exact: true }).click();
|
||||||
|
await page.getByPlaceholder("Send a message...").fill("Hello, how are you?");
|
||||||
|
await page.getByTestId("icon-LucideSend").click();
|
||||||
|
let valueUser = await page.getByTestId("sender_name_user").textContent();
|
||||||
|
let valueAI = await page.getByTestId("sender_name_ai").textContent();
|
||||||
|
|
||||||
|
expect(valueUser).toBe("User");
|
||||||
|
expect(valueAI).toBe("AI");
|
||||||
|
|
||||||
|
await page.getByTestId("icon-Eraser").last().click();
|
||||||
|
|
||||||
|
await page.getByText("Hello, how are you?").isHidden();
|
||||||
|
await page.getByText("AI", { exact: true }).last().isHidden();
|
||||||
|
await page.getByText("User", { exact: true }).last().isHidden();
|
||||||
|
await page.getByText("Start a conversation").isVisible();
|
||||||
|
await page.getByText("Langflow Chat").isVisible();
|
||||||
|
});
|
||||||
|
|
@ -95,5 +95,33 @@ test("should see shortcuts", async ({ page }) => {
|
||||||
await page.getByText("Delete Component", { exact: true }).isVisible();
|
await page.getByText("Delete Component", { exact: true }).isVisible();
|
||||||
await page.getByText("Open Playground", { exact: true }).isVisible();
|
await page.getByText("Open Playground", { exact: true }).isVisible();
|
||||||
await page.getByText("Undo", { exact: true }).isVisible();
|
await page.getByText("Undo", { exact: true }).isVisible();
|
||||||
await page.getByText("Redo", { exact: true }).isVisible();
|
|
||||||
|
await page.mouse.wheel(0, 10000);
|
||||||
|
|
||||||
|
await page.getByText("Redo", { exact: true }).last().isVisible();
|
||||||
|
|
||||||
|
await page.getByText("Reset Columns").last().isVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should interact with API Keys", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
await page.getByTestId("user-profile-settings").click();
|
||||||
|
await page.getByText("Settings").click();
|
||||||
|
await page.getByText("API Keys").click();
|
||||||
|
await page.getByText("API Keys", { exact: true }).nth(1).isVisible();
|
||||||
|
await page.getByText("Add New").click();
|
||||||
|
await page.getByPlaceholder("Insert a name for your API Key").isVisible();
|
||||||
|
|
||||||
|
const randomName = Math.random().toString(36).substring(2);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByPlaceholder("Insert a name for your API Key")
|
||||||
|
.fill(randomName);
|
||||||
|
await page.getByText("Create Secret Key", { exact: true }).click();
|
||||||
|
await page.getByText("Please save").isVisible();
|
||||||
|
await page.getByTestId("icon-Copy").click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.getByText("Api Key Copied!").isVisible();
|
||||||
|
await page.getByText(randomName).isVisible();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue