Added API Keys into Settings, and made changes into BaseModal (#2067)
This pull request encompasses a series of updates and bug fixes aimed at improving the overall functionality, user experience, and UI consistency of the application. The changes include enhancements to modals, buttons, toolbars, and various other components. Below is a summary of the key changes made: - Used TableComponent as default to show API Keys - Changed Backend to show Created At - Transferred info from API Keys page to API Keys section of the Settings page - Refactored API Keys adding modal - Refactored BaseModal
This commit is contained in:
commit
0631226cd8
44 changed files with 1069 additions and 1100 deletions
|
|
@ -9,7 +9,7 @@ The `NotionUserList` component retrieves users from Notion. It provides a conven
|
|||
|
||||
[Notion Reference](https://developers.notion.com/reference/get-users)
|
||||
|
||||
The `NotionUserList` component enables you to:
|
||||
The `NotionUserList` component enables you to:
|
||||
|
||||
- Retrieve user data from Notion
|
||||
- Access user information such as ID, type, name, and avatar URL
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class ApiKeyRead(ApiKeyBase):
|
|||
id: UUID
|
||||
api_key: str = Field(schema_extra={"validate_default": True})
|
||||
user_id: UUID = Field()
|
||||
created_at: datetime = Field()
|
||||
|
||||
@field_validator("api_key")
|
||||
@classmethod
|
||||
|
|
|
|||
18
src/frontend/package-lock.json
generated
18
src/frontend/package-lock.json
generated
|
|
@ -44,6 +44,7 @@
|
|||
"debounce-promise": "^3.1.2",
|
||||
"dompurify": "^3.0.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"esbuild": "^0.17.19",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^11.0.6",
|
||||
|
|
@ -479,6 +480,7 @@
|
|||
},
|
||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -5997,9 +5999,9 @@
|
|||
"integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA=="
|
||||
},
|
||||
"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=="
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
|
|
@ -12204,6 +12206,16 @@
|
|||
"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": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"debounce-promise": "^3.1.2",
|
||||
"dompurify": "^3.0.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"esbuild": "^0.17.19",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^11.0.6",
|
||||
|
|
|
|||
|
|
@ -164,3 +164,13 @@ body {
|
|||
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
|
||||
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 PopoverContentDropdown = children
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
const PopoverContentDropdown =
|
||||
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -181,18 +181,6 @@ export default function Header(): JSX.Element {
|
|||
/>
|
||||
</div>
|
||||
</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" />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import {
|
|||
CommandList,
|
||||
} from "../../../ui/command";
|
||||
import { Input } from "../../../ui/input";
|
||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverContentWithoutPortal,
|
||||
} from "../../../ui/popover";
|
||||
const CustomInputPopover = ({
|
||||
id,
|
||||
refInput,
|
||||
|
|
@ -39,6 +43,9 @@ const CustomInputPopover = ({
|
|||
showOptions,
|
||||
}) => {
|
||||
const setErrorData = useAlertStore.getState().setErrorData;
|
||||
const PopoverContentInput = editNode
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
if (password) {
|
||||
|
|
@ -107,7 +114,7 @@ const CustomInputPopover = ({
|
|||
data-testid={editNode ? id + "-edit" : id}
|
||||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContentWithoutPortal
|
||||
<PopoverContentInput
|
||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||
side="bottom"
|
||||
|
|
@ -184,7 +191,7 @@ const CustomInputPopover = ({
|
|||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContentWithoutPortal>
|
||||
</PopoverContentInput>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import {
|
|||
CommandList,
|
||||
} from "../../../ui/command";
|
||||
import { Input } from "../../../ui/input";
|
||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverContentWithoutPortal,
|
||||
} from "../../../ui/popover";
|
||||
const CustomInputPopoverObject = ({
|
||||
id,
|
||||
refInput,
|
||||
|
|
@ -23,6 +27,7 @@ const CustomInputPopoverObject = ({
|
|||
disabled,
|
||||
setShowOptions,
|
||||
required,
|
||||
editNode,
|
||||
className,
|
||||
placeholder,
|
||||
onChange,
|
||||
|
|
@ -34,6 +39,10 @@ const CustomInputPopoverObject = ({
|
|||
handleKeyDown,
|
||||
showOptions,
|
||||
}) => {
|
||||
const PopoverContentInput = editNode
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
onChange && onChange(e.target.value);
|
||||
};
|
||||
|
|
@ -79,7 +88,7 @@ const CustomInputPopoverObject = ({
|
|||
data-testid={id}
|
||||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContentWithoutPortal
|
||||
<PopoverContentInput
|
||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||
side="bottom"
|
||||
|
|
@ -159,7 +168,7 @@ const CustomInputPopoverObject = ({
|
|||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContentWithoutPortal>
|
||||
</PopoverContentInput>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export default function InputComponent({
|
|||
setSelectedOptions={setSelectedOptions}
|
||||
options={objectOptions}
|
||||
value={value}
|
||||
editNode={editNode}
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
setShowOptions={setShowOptions}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ export default function InputGlobalComponent({
|
|||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.node?.template[name])
|
||||
if (data)
|
||||
if (
|
||||
globalVariablesEntries &&
|
||||
!globalVariablesEntries.includes(data.node?.template[name].value) &&
|
||||
data.node?.template[name].load_from_db
|
||||
!globalVariablesEntries.includes(data.value) &&
|
||||
data.load_from_db
|
||||
) {
|
||||
setTimeout(() => {
|
||||
onChange("", true);
|
||||
|
|
@ -46,17 +46,11 @@ export default function InputGlobalComponent({
|
|||
}, [globalVariablesEntries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!data.node?.template[name].value &&
|
||||
data.node?.template[name].display_name
|
||||
) {
|
||||
if (
|
||||
unavaliableFields[data.node?.template[name].display_name!] &&
|
||||
!disabled
|
||||
) {
|
||||
if (!data.value && data.display_name) {
|
||||
if (unavaliableFields[data.display_name!] && !disabled) {
|
||||
setTimeout(() => {
|
||||
setDb(true);
|
||||
onChange(unavaliableFields[data.node?.template[name].display_name!]);
|
||||
onChange(unavaliableFields[data.display_name!]);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
|
@ -68,10 +62,7 @@ export default function InputGlobalComponent({
|
|||
await deleteGlobalVariable(id)
|
||||
.then(() => {
|
||||
removeGlobalVariable(key);
|
||||
if (
|
||||
data?.node?.template[name].value === key &&
|
||||
data?.node?.template[name].load_from_db
|
||||
) {
|
||||
if (data?.value === key && data?.load_from_db) {
|
||||
onChange("");
|
||||
setDb(false);
|
||||
}
|
||||
|
|
@ -94,8 +85,8 @@ export default function InputGlobalComponent({
|
|||
id={"input-" + name}
|
||||
editNode={editNode}
|
||||
disabled={disabled}
|
||||
password={data.node?.template[name].password ?? false}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
password={data.password ?? false}
|
||||
value={data.value ?? ""}
|
||||
options={globalVariablesEntries}
|
||||
optionsPlaceholder={"Global Variables"}
|
||||
optionsIcon="Globe"
|
||||
|
|
@ -138,10 +129,10 @@ export default function InputGlobalComponent({
|
|||
</DeleteConfirmationModal>
|
||||
)}
|
||||
selectedOption={
|
||||
data?.node?.template[name].load_from_db &&
|
||||
data?.load_from_db &&
|
||||
globalVariablesEntries &&
|
||||
globalVariablesEntries.includes(data?.node?.template[name].value ?? "")
|
||||
? data?.node?.template[name].value
|
||||
globalVariablesEntries.includes(data?.value ?? "")
|
||||
? data?.value
|
||||
: ""
|
||||
}
|
||||
setSelectedOption={(value) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function ShadTooltip({
|
|||
delayDuration = 500,
|
||||
}: ShadToolTipType): JSX.Element {
|
||||
return (
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<Tooltip defaultOpen={!children} delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className={cn(styleClasses, "max-w-96")}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { CustomCellRendererProps } from "ag-grid-react";
|
||||
import { cn, isTimeStampString } from "../../utils/utils";
|
||||
import ArrayReader from "../arrayReaderComponent";
|
||||
import DateReader from "../dateReaderComponent";
|
||||
import NumberReader from "../numberReader";
|
||||
import ObjectRender from "../objectRender";
|
||||
import StringReader from "../stringReaderComponent";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { cn, isTimeStampString } from "../../../../utils/utils";
|
||||
import ArrayReader from "../../../arrayReaderComponent";
|
||||
import DateReader from "../../../dateReaderComponent";
|
||||
import NumberReader from "../../../numberReader";
|
||||
import ObjectRender from "../../../objectRender";
|
||||
import StringReader from "../../../stringReaderComponent";
|
||||
import { Badge } from "../../../ui/badge";
|
||||
|
||||
export default function TableAutoCellRender({
|
||||
value,
|
||||
|
|
@ -43,7 +43,6 @@ export default function TableAutoCellRender({
|
|||
} else {
|
||||
return <StringReader string={value} />;
|
||||
}
|
||||
break;
|
||||
case "number":
|
||||
return <NumberReader number={value} />;
|
||||
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}
|
||||
ref={realRef}
|
||||
getRowId={(params) => {
|
||||
return params.data.id;
|
||||
}}
|
||||
pagination={true}
|
||||
onGridReady={onGridReady}
|
||||
onColumnMoved={onColumnMoved}
|
||||
|
|
|
|||
|
|
@ -29,20 +29,18 @@ export default function ToggleShadComponent({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
|
||||
<Switch
|
||||
id={id}
|
||||
data-testid={id}
|
||||
style={{
|
||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
}}
|
||||
disabled={disabled}
|
||||
className=""
|
||||
checked={enabled}
|
||||
onCheckedChange={(isEnabled: boolean) => {
|
||||
setEnabled(isEnabled);
|
||||
}}
|
||||
></Switch>
|
||||
</div>
|
||||
<Switch
|
||||
id={id}
|
||||
data-testid={id}
|
||||
style={{
|
||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
}}
|
||||
disabled={disabled}
|
||||
className=""
|
||||
checked={enabled}
|
||||
onCheckedChange={(isEnabled: boolean) => {
|
||||
setEnabled(isEnabled);
|
||||
}}
|
||||
></Switch>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,7 @@ function RefreshButton({
|
|||
|
||||
// icon class name should take into account the disabled state and the loading state
|
||||
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
|
||||
const iconClassName = cn(
|
||||
"h-4 w-4",
|
||||
isLoading ? "animate-spin" : "animate-wiggle",
|
||||
disabledIconTextClass
|
||||
);
|
||||
const iconClassName = cn("h-4 w-4 animate-wiggle", disabledIconTextClass);
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
|
@ -44,10 +40,11 @@ function RefreshButton({
|
|||
className={classNames}
|
||||
onClick={handleClick}
|
||||
id={id}
|
||||
loading={isLoading}
|
||||
>
|
||||
{button_text && <span className="mr-1">{button_text}</span>}
|
||||
<IconComponent
|
||||
name={isLoading ? "Loader2" : "RefreshCcw"}
|
||||
name={"RefreshCcw"}
|
||||
className={iconClassName}
|
||||
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 SIGNIN_ERROR_ALERT = "Error signing in";
|
||||
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 WRONG_FILE_ERROR_ALERT = "Invalid file type";
|
||||
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_ADD_SUCCESS_ALERT = "Success! New user added!";
|
||||
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 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 API_PAGE_PARAGRAPH_1 =
|
||||
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
|
||||
|
||||
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_PARAGRAPH =
|
||||
"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_USER_KEYS =
|
||||
"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 refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
|
|
@ -392,7 +391,7 @@ export default function ParameterComponent({
|
|||
});
|
||||
}}
|
||||
name={name}
|
||||
data={data}
|
||||
data={data.node?.template[name]}
|
||||
/>
|
||||
</div>
|
||||
{data.node?.template[name]?.refresh_button && (
|
||||
|
|
@ -448,8 +447,8 @@ export default function ParameterComponent({
|
|||
data.node?.template[name]?.real_time_refresh)
|
||||
}
|
||||
>
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div className="w-5/6 flex-grow">
|
||||
<div className="mt-2 flex w-full items-center gap-2">
|
||||
<div className="flex-1">
|
||||
<Dropdown
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
|
|
@ -467,7 +466,6 @@ export default function ParameterComponent({
|
|||
name={name}
|
||||
data={data}
|
||||
button_text={data.node?.template[name]?.refresh_button_text}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import emojiRegex from "emoji-regex";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
|
|
@ -220,8 +221,7 @@ export default function GenericNode({
|
|||
|
||||
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 iconElement = data?.node?.icon;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ const ApiModal = forwardRef(
|
|||
);
|
||||
const pythonCode = getPythonCode(flow?.name, tweak);
|
||||
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
||||
console.log("flow", flow);
|
||||
const includeWebhook = flow.webhook;
|
||||
const tweaksCode = buildTweaks(flow);
|
||||
const codesArray = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export const switchCaseModalSize = (size: string) => {
|
|||
switch (size) {
|
||||
case "x-small":
|
||||
minWidth = "min-w-[20vw]";
|
||||
height = "h-full";
|
||||
height = "";
|
||||
break;
|
||||
case "smaller":
|
||||
minWidth = "min-w-[40vw]";
|
||||
|
|
@ -12,7 +12,7 @@ export const switchCaseModalSize = (size: string) => {
|
|||
break;
|
||||
case "smaller-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
height = "";
|
||||
break;
|
||||
case "small":
|
||||
minWidth = "min-w-[40vw]";
|
||||
|
|
@ -20,16 +20,19 @@ export const switchCaseModalSize = (size: string) => {
|
|||
break;
|
||||
case "small-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
height = "";
|
||||
break;
|
||||
case "medium":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[60vh]";
|
||||
break;
|
||||
case "medium-tall":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[90vh]";
|
||||
break;
|
||||
case "medium-h-full":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-full";
|
||||
|
||||
height = "";
|
||||
break;
|
||||
case "large":
|
||||
minWidth = "min-w-[85vw]";
|
||||
|
|
@ -56,11 +59,11 @@ export const switchCaseModalSize = (size: string) => {
|
|||
|
||||
case "large-h-full":
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-full";
|
||||
height = "";
|
||||
break;
|
||||
default:
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
height = "h-[90vh]";
|
||||
break;
|
||||
}
|
||||
return { minWidth, height };
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { Button } from "../../components/ui/button";
|
|||
import { modalHeaderType } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
|
||||
type ContentProps = { children: ReactNode };
|
||||
type HeaderProps = { children: ReactNode; description: string };
|
||||
|
|
@ -52,10 +53,10 @@ const Trigger: React.FC<TriggerProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
||||
children,
|
||||
description,
|
||||
}: modalHeaderType): JSX.Element => {
|
||||
const Header: React.FC<{
|
||||
children: ReactNode;
|
||||
description: string | JSX.Element | null;
|
||||
}> = ({ children, description }: modalHeaderType): JSX.Element => {
|
||||
return (
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
||||
|
|
@ -111,6 +112,7 @@ interface BaseModalProps {
|
|||
| "smaller"
|
||||
| "small"
|
||||
| "medium"
|
||||
| "medium-tall"
|
||||
| "large"
|
||||
| "three-cards"
|
||||
| "large-thin"
|
||||
|
|
@ -179,31 +181,33 @@ function BaseModal({
|
|||
) : (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{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">
|
||||
{headerChild}
|
||||
</div>
|
||||
{onSubmit ? (
|
||||
<form
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
className="flex flex-col gap-6"
|
||||
className="flex flex-1 flex-col gap-6"
|
||||
>
|
||||
<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}
|
||||
</div>
|
||||
{ContentFooter && (
|
||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||
)}
|
||||
</form>
|
||||
</Form.Root>
|
||||
) : (
|
||||
<>
|
||||
<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}
|
||||
</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 { forwardRef, useEffect, useState } from "react";
|
||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||
import DictComponent from "../../components/dictComponent";
|
||||
import Dropdown from "../../components/dropdownComponent";
|
||||
import FloatComponent from "../../components/floatComponent";
|
||||
import { ColDef, GridApi } from "ag-grid-community";
|
||||
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import InputFileComponent from "../../components/inputFileComponent";
|
||||
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 TableComponent from "../../components/tableComponent";
|
||||
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 { NodeDataType } from "../../types/flow";
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
scapedJSONStringfy,
|
||||
} from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
import useColumnDefs from "./hooks/use-column-defs";
|
||||
import useRowData from "./hooks/use-row-data";
|
||||
|
||||
const EditNodeModal = forwardRef(
|
||||
(
|
||||
|
|
@ -54,59 +24,49 @@ const EditNodeModal = forwardRef(
|
|||
},
|
||||
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);
|
||||
|
||||
function changeAdvanced(n) {
|
||||
setMyData((old) => {
|
||||
let newData = cloneDeep(old);
|
||||
newData.node!.template[n].advanced =
|
||||
!newData.node!.template[n].advanced;
|
||||
return newData;
|
||||
});
|
||||
myData.current.node!.template[n].advanced =
|
||||
!myData.current.node!.template[n]?.advanced;
|
||||
}
|
||||
|
||||
const handleOnNewValue = (newValue: any, name) => {
|
||||
setMyData((old) => {
|
||||
let newData = cloneDeep(old);
|
||||
newData.node!.template[name].value = newValue;
|
||||
return newData;
|
||||
});
|
||||
myData.current.node!.template[name].value = newValue;
|
||||
};
|
||||
|
||||
const rowData = useRowData(data, open);
|
||||
|
||||
const columnDefs: ColDef[] = useColumnDefs(
|
||||
data,
|
||||
handleOnNewValue,
|
||||
changeAdvanced,
|
||||
open
|
||||
);
|
||||
|
||||
const [gridApi, setGridApi] = useState<GridApi | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setMyData(data); // reset data to what it is on node when opening modal
|
||||
if (gridApi && open) {
|
||||
myData.current = data;
|
||||
gridApi.refreshCells();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
||||
const type = (templateParam) => {
|
||||
return myData.node?.template[templateParam].type;
|
||||
};
|
||||
}, [gridApi, open]);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
key={data.id}
|
||||
size="large-h-full"
|
||||
size="medium-tall"
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
onChangeOpenModal={(open) => {
|
||||
setMyData(data);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: myData.node,
|
||||
node: myData.current.node,
|
||||
},
|
||||
}));
|
||||
setOpen(false);
|
||||
|
|
@ -115,526 +75,30 @@ const EditNodeModal = forwardRef(
|
|||
<BaseModal.Trigger>
|
||||
<></>
|
||||
</BaseModal.Trigger>
|
||||
<BaseModal.Header description={myData.node?.description!}>
|
||||
<span className="pr-2">{myData.type}</span>
|
||||
<Badge variant="secondary">ID: {myData.id}</Badge>
|
||||
<BaseModal.Header description={data.node?.description!}>
|
||||
<span className="pr-2">{data.type}</span>
|
||||
<Badge variant="secondary">ID: {data.id}</Badge>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<div className="flex pb-2">
|
||||
<IconComponent
|
||||
name="Variable"
|
||||
className="edit-node-modal-variable "
|
||||
/>
|
||||
<span className="edit-node-modal-span">Parameters</span>
|
||||
</div>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex pb-2">
|
||||
<IconComponent
|
||||
name="Variable"
|
||||
className="edit-node-modal-variable "
|
||||
/>
|
||||
<span className="edit-node-modal-span">Parameters</span>
|
||||
</div>
|
||||
|
||||
<div className="edit-node-modal-arrangement">
|
||||
<div
|
||||
className={classNames(
|
||||
"edit-node-modal-box",
|
||||
nodeLength > limitScrollFieldsModal
|
||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<div className="h-full">
|
||||
{nodeLength > 0 && (
|
||||
<div className="edit-node-modal-table">
|
||||
<Table className="table-fixed bg-muted outline-1">
|
||||
<TableHeader className="edit-node-modal-table-header">
|
||||
<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
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : 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>
|
||||
<TableComponent
|
||||
onGridReady={(params) => {
|
||||
setGridApi(params.api);
|
||||
}}
|
||||
tooltipShowDelay={0.5}
|
||||
columnDefs={columnDefs}
|
||||
rowData={rowData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,15 +11,10 @@ import { nodeIconsLucide } from "../../utils/styleUtils";
|
|||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function SecretKeyModal({
|
||||
title,
|
||||
cancelText,
|
||||
confirmationText,
|
||||
children,
|
||||
icon,
|
||||
data,
|
||||
onCloseModal,
|
||||
}: ApiKeyType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
const [open, setOpen] = useState(false);
|
||||
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||
|
|
@ -66,118 +61,91 @@ export default function SecretKeyModal({
|
|||
.catch((err) => {});
|
||||
}
|
||||
|
||||
function handleSubmitForm() {
|
||||
if (!renderKey) {
|
||||
setRenderKey(true);
|
||||
handleAddNewKey();
|
||||
} else {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
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.Header description={""}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
{renderKey === true && (
|
||||
<>
|
||||
<span className="text-xs">
|
||||
<BaseModal.Header
|
||||
description={
|
||||
renderKey ? (
|
||||
<>
|
||||
{" "}
|
||||
Please save this secret key somewhere safe and accessible. For
|
||||
security reasons,{" "}
|
||||
<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
|
||||
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">
|
||||
<Input ref={inputRef} readOnly={true} value={apiKeyValue} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
className="ml-3"
|
||||
onClick={() => {
|
||||
handleCopyClick();
|
||||
}}
|
||||
>
|
||||
{textCopied ? (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleCopyClick();
|
||||
}}
|
||||
variant="none"
|
||||
size="none"
|
||||
>
|
||||
{textCopied ? (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Root
|
||||
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.Field name="apikey">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
//fake api key
|
||||
id="primary-input"
|
||||
value={apiKeyName}
|
||||
ref={inputRef}
|
||||
onChange={({ target: { value } }) => {
|
||||
setApiKeyName(value);
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Name (optional){" "}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={({ target: { value } }) => {
|
||||
setApiKeyName(value);
|
||||
}}
|
||||
value={apiKeyName}
|
||||
className="primary-input"
|
||||
placeholder="My key name"
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
placeholder="Insert a name for your API Key"
|
||||
/>
|
||||
</Form.Control>
|
||||
</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>
|
||||
</Form.Field>
|
||||
)}
|
||||
</BaseModal.Content>
|
||||
<BaseModal.Footer
|
||||
submit={{ label: renderKey ? "Done" : "Create Secret Key" }}
|
||||
/>
|
||||
</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",
|
||||
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",
|
||||
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 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 FlowPage = lazy(() => import("./pages/FlowPage"));
|
||||
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
||||
|
|
@ -77,6 +79,7 @@ const Router = () => {
|
|||
>
|
||||
<Route index element={<Navigate replace to={"general"} />} />
|
||||
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
||||
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||
<Route path="messages" element={<MessagesPage />} />
|
||||
|
|
@ -189,14 +192,6 @@ const Router = () => {
|
|||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
<Route
|
||||
path="api-keys"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ApiKeysPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -100,3 +100,13 @@ select:-webkit-autofill:focus {
|
|||
.json-view-dark {
|
||||
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 = {
|
||||
enabled: boolean;
|
||||
setEnabled: (state: boolean) => void;
|
||||
disabled: boolean | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
size: "small" | "medium" | "large";
|
||||
id?: string;
|
||||
editNode?: boolean;
|
||||
|
|
@ -393,11 +393,7 @@ export type UserInputType = {
|
|||
};
|
||||
|
||||
export type ApiKeyType = {
|
||||
title: string;
|
||||
cancelText: string;
|
||||
confirmationText: string;
|
||||
children: ReactElement;
|
||||
icon: string;
|
||||
data?: any;
|
||||
onCloseModal: () => void;
|
||||
};
|
||||
|
|
@ -547,7 +543,7 @@ export type iconsType = {
|
|||
|
||||
export type modalHeaderType = {
|
||||
children: ReactNode;
|
||||
description: string | null;
|
||||
description: string | JSX.Element | null;
|
||||
};
|
||||
|
||||
export type codeAreaModalPropsType = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import clsx, { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import TableAutoCellRender from "../components/tableAutoCellRender";
|
||||
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
||||
import { APIDataType, TemplateVariableType } from "../types/api";
|
||||
import {
|
||||
groupedObjType,
|
||||
|
|
@ -345,8 +345,10 @@ export function freezeObject(obj: any) {
|
|||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
export function isTimeStampString(str: string): boolean {
|
||||
const timestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
||||
return timestampRegex.test(str);
|
||||
const timestampRegexA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ test("dropDownComponent", async ({ page }) => {
|
|||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByTestId("more-options-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("Open Playground", { 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