Merge branch 'dev' into saveComponent
This commit is contained in:
commit
f0c64112ea
249 changed files with 13454 additions and 5022 deletions
2
src/frontend/.gitignore
vendored
2
src/frontend/.gitignore
vendored
|
|
@ -22,5 +22,5 @@ npm-debug.log*
|
|||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright-report/*/
|
||||
/playwright/.cache/
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
FROM node:14-alpine as frontend_build
|
||||
ARG BACKEND
|
||||
FROM node:20-alpine as frontend_build
|
||||
ARG BACKEND_URL
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
COPY ./package.json ./package-lock.json ./tsconfig.json ./vite.config.ts ./index.html ./tailwind.config.js ./postcss.config.js ./prettier.config.js /app/
|
||||
RUN npm install
|
||||
COPY ./src /app/src
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx
|
||||
COPY --from=frontend_build /app/build/ /usr/share/nginx/html
|
||||
COPY /nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY /nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY start-nginx.sh /start-nginx.sh
|
||||
RUN chmod +x /start-nginx.sh
|
||||
ENV BACKEND_URL=$BACKEND_URL
|
||||
CMD ["/start-nginx.sh"]
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
server {
|
||||
gzip on;
|
||||
gzip_comp_level 2;
|
||||
gzip_min_length 1000;
|
||||
gzip_types text/xml text/css;
|
||||
gzip_http_version 1.1;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [4-6] \.";
|
||||
gzip on;
|
||||
gzip_comp_level 2;
|
||||
gzip_min_length 1000;
|
||||
gzip_types text/xml text/css;
|
||||
gzip_http_version 1.1;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [4-6] \.";
|
||||
|
||||
listen 80;
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
|
||||
include /etc/nginx/extra-conf.d/*.conf;
|
||||
}
|
||||
|
|
|
|||
758
src/frontend/package-lock.json
generated
758
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -54,7 +54,7 @@
|
|||
"react-tabs": "^6.0.2",
|
||||
"react-tooltip": "^5.21.1",
|
||||
"react18-json-view": "^0.2.3",
|
||||
"reactflow": "^11.8.3",
|
||||
"reactflow": "^11.9.2",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||
"format": "npx prettier --write \"./**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
|
|
|||
18
src/frontend/playwright-report/index.html
Normal file
18
src/frontend/playwright-report/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="./e2e/index.html">e2e report</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="./onlyFront/index.html">frontEnd Only report</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -20,11 +20,13 @@ export default defineConfig({
|
|||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
reporter: [
|
||||
["html", { open: "never", outputFolder: "playwright-report/test-results" }],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
// baseURL: "http://127.0.0.1:3000",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
|
@ -69,9 +71,16 @@ export default defineConfig({
|
|||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
// webServer: [
|
||||
// {
|
||||
// command: "npm run backend",
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// timeout: 120 * 1000,
|
||||
// },
|
||||
// {
|
||||
// command: "npm run start",
|
||||
// url: "http://127.0.0.1:3000",
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
// ],
|
||||
});
|
||||
|
|
|
|||
75
src/frontend/run-tests.sh
Executable file
75
src/frontend/run-tests.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Default value for the --ui flag
|
||||
ui=false
|
||||
|
||||
# Parse command-line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
case $key in
|
||||
--ui)
|
||||
ui=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $key"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Function to forcibly terminate a process by port
|
||||
terminate_process_by_port() {
|
||||
port="$1"
|
||||
echo "Terminating process on port: $port"
|
||||
fuser -k -n tcp "$port" # Forcefully terminate processes using the specified port
|
||||
echo "Process terminated."
|
||||
}
|
||||
|
||||
# Trap signals to ensure cleanup on script termination
|
||||
trap 'terminate_process_by_port 7860; terminate_process_by_port 3000' EXIT
|
||||
|
||||
# Navigate to the project root directory (where the Makefile is located)
|
||||
cd ../../
|
||||
|
||||
# Start the frontend using 'make frontend' in the background
|
||||
make frontend &
|
||||
|
||||
# Give some time for the frontend to start (adjust sleep duration as needed)
|
||||
sleep 10
|
||||
|
||||
# Navigate to the test directory
|
||||
cd src/frontend
|
||||
|
||||
# Run frontend only Playwright tests with or without UI based on the --ui flag
|
||||
if [ "$ui" = true ]; then
|
||||
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --ui --project=chromium
|
||||
else
|
||||
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --project=chromium
|
||||
fi
|
||||
|
||||
# Navigate back to the project root directory
|
||||
cd ../../
|
||||
|
||||
# Start the backend using 'make backend' in the background
|
||||
make backend &
|
||||
|
||||
# Give some time for the backend to start (adjust sleep duration as needed)
|
||||
sleep 10
|
||||
|
||||
# Navigate back to the test directory
|
||||
cd src/frontend
|
||||
|
||||
# Run Playwright tests with or without UI based on the --ui flag
|
||||
if [ "$ui" = true ]; then
|
||||
PLAYWRIGHT_HTML_REPORT=playwright-report/e2e npx playwright test tests/end-to-end --ui --project=chromium
|
||||
else
|
||||
PLAYWRIGHT_HTML_REPORT=playwright-report/e2e npx playwright test tests/end-to-end --project=chromium
|
||||
fi
|
||||
|
||||
npx playwright show-report
|
||||
|
||||
# After the tests are finished, you can add cleanup or teardown logic here if needed
|
||||
|
||||
# The trap will automatically terminate processes by port on script exit
|
||||
|
|
@ -32,6 +32,7 @@ import {
|
|||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
isValidConnection,
|
||||
scapedJSONStringfy,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import {
|
||||
nodeColors,
|
||||
|
|
@ -53,6 +54,7 @@ export default function ParameterComponent({
|
|||
required = false,
|
||||
optionalHandle = null,
|
||||
info = "",
|
||||
proxy,
|
||||
showNode,
|
||||
}: ParameterComponentType): JSX.Element {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -60,7 +62,7 @@ export default function ParameterComponent({
|
|||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [position, setPosition] = useState(0);
|
||||
const { setTabsState, tabId, save, flows } = useContext(TabsContext);
|
||||
const { setTabsState, tabId, flows } = useContext(TabsContext);
|
||||
|
||||
const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null;
|
||||
|
||||
|
|
@ -80,8 +82,9 @@ export default function ParameterComponent({
|
|||
|
||||
const { reactFlowInstance, setFilterEdge } = useContext(typesContext);
|
||||
let disabled =
|
||||
reactFlowInstance?.getEdges().some((edge) => edge.targetHandle === id) ??
|
||||
false;
|
||||
reactFlowInstance
|
||||
?.getEdges()
|
||||
.some((edge) => edge.targetHandle === scapedJSONStringfy(id)) ?? false;
|
||||
|
||||
const { data: myData } = useContext(typesContext);
|
||||
|
||||
|
|
@ -112,7 +115,6 @@ export default function ParameterComponent({
|
|||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (name === "openai_api_base") console.log(info);
|
||||
// @ts-ignore
|
||||
infoHtml.current = (
|
||||
<div className="h-full w-full break-words">
|
||||
|
|
@ -136,7 +138,7 @@ export default function ParameterComponent({
|
|||
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div key={index}>
|
||||
{index === 0 && (
|
||||
<span>
|
||||
{left
|
||||
|
|
@ -183,7 +185,7 @@ export default function ParameterComponent({
|
|||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
|
|
@ -204,33 +206,46 @@ export default function ParameterComponent({
|
|||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int") &&
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict") &&
|
||||
!optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<ShadTooltip
|
||||
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
|
||||
delayDuration={0}
|
||||
content={refHtml.current}
|
||||
side={left ? "left" : "right"}
|
||||
>
|
||||
<Handle
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
id={id}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, reactFlowInstance!)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
|
||||
"h-3 w-3 rounded-full border-2 bg-background"
|
||||
)}
|
||||
style={{
|
||||
borderColor: color,
|
||||
top: position,
|
||||
}}
|
||||
></Handle>
|
||||
</ShadTooltip>
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
<div className="flex">
|
||||
<ShadTooltip
|
||||
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
|
||||
delayDuration={0}
|
||||
content={refHtml.current}
|
||||
side={left ? "left" : "right"}
|
||||
>
|
||||
<Handle
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
id={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, reactFlowInstance!)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
|
||||
"h-3 w-3 rounded-full border-2 bg-background"
|
||||
)}
|
||||
style={{
|
||||
borderColor: color,
|
||||
top: position,
|
||||
}}
|
||||
onClick={() => {
|
||||
setFilterEdge(groupedEdge.current);
|
||||
}}
|
||||
></Handle>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
|
|
@ -245,7 +260,13 @@ export default function ParameterComponent({
|
|||
(info !== "" ? " flex items-center" : "")
|
||||
}
|
||||
>
|
||||
{title}
|
||||
{proxy ? (
|
||||
<ShadTooltip content={<span>{proxy.id}</span>}>
|
||||
<span>{title}</span>
|
||||
</ShadTooltip>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
<span className="text-status-red">{required ? " *" : ""}</span>
|
||||
<div className="">
|
||||
{info !== "" && (
|
||||
|
|
@ -268,9 +289,9 @@ export default function ParameterComponent({
|
|||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict" ||
|
||||
type === "int") &&
|
||||
type === "NestedDict") &&
|
||||
!optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
|
|
@ -285,7 +306,11 @@ export default function ParameterComponent({
|
|||
<Handle
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
id={id}
|
||||
id={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, reactFlowInstance!)
|
||||
}
|
||||
|
|
@ -368,6 +393,11 @@ export default function ParameterComponent({
|
|||
) : left === true && type === "code" ? (
|
||||
<div className="mt-2 w-full">
|
||||
<CodeAreaComponent
|
||||
readonly={
|
||||
data.node?.flow && data.node.template[name].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
dynamic={data.node?.template[name].dynamic ?? false}
|
||||
setNodeClass={(nodeClass) => {
|
||||
data.node = nodeClass;
|
||||
|
|
@ -388,7 +418,6 @@ export default function ParameterComponent({
|
|||
suffixes={data.node?.template[name].suffixes}
|
||||
onFileChange={(filePath: string) => {
|
||||
data.node!.template[name].file_path = filePath;
|
||||
save();
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
|
|
@ -403,6 +432,11 @@ export default function ParameterComponent({
|
|||
) : left === true && type === "prompt" ? (
|
||||
<div className="mt-2 w-full">
|
||||
<PromptAreaComponent
|
||||
readonly={
|
||||
data.node?.flow && data.node.template[name].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
field_name={name}
|
||||
setNodeClass={(nodeClass) => {
|
||||
data.node = nodeClass;
|
||||
|
|
@ -424,9 +458,12 @@ export default function ParameterComponent({
|
|||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
data.node!.template[name].value ?? {
|
||||
yourkey: "value",
|
||||
}
|
||||
!data.node!.template[name].value ||
|
||||
data.node!.template[name].value?.toString() === "{}"
|
||||
? {
|
||||
yourkey: "value",
|
||||
}
|
||||
: data.node!.template[name].value
|
||||
}
|
||||
onChange={(newValue) => {
|
||||
data.node!.template[name].value = newValue;
|
||||
|
|
|
|||
|
|
@ -4,23 +4,33 @@ import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
|||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import Tooltip from "../../components/TooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { useSSE } from "../../contexts/SSEContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||
import { validationStatusType } from "../../types/components";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { cleanEdges } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
cleanEdges,
|
||||
handleKeyDown,
|
||||
scapedJSONStringfy,
|
||||
} from "../../utils/reactflowUtils";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import { classNames, toTitleCase } from "../../utils/utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
|
||||
export default function GenericNode({
|
||||
data: olddata,
|
||||
xPos,
|
||||
yPos,
|
||||
selected,
|
||||
}: {
|
||||
data: NodeDataType;
|
||||
selected: boolean;
|
||||
xPos: number;
|
||||
yPos: number;
|
||||
}): JSX.Element {
|
||||
const [data, setData] = useState(olddata);
|
||||
const { updateFlow, flows, tabId } = useContext(TabsContext);
|
||||
|
|
@ -28,6 +38,12 @@ export default function GenericNode({
|
|||
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
|
||||
useContext(typesContext);
|
||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||
const [inputName, setInputName] = useState(true);
|
||||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!
|
||||
);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
useState<validationStatusType | null>(null);
|
||||
const [showNode, setShowNode] = useState<boolean>(true);
|
||||
|
|
@ -107,11 +123,11 @@ export default function GenericNode({
|
|||
setValidationStatus(null);
|
||||
}
|
||||
}, [sseData, data.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NodeToolbar>
|
||||
<NodeToolbarComponent
|
||||
position={{ x: xPos, y: yPos }}
|
||||
data={data}
|
||||
setData={setData}
|
||||
deleteNode={deleteNode}
|
||||
|
|
@ -152,7 +168,7 @@ export default function GenericNode({
|
|||
}
|
||||
>
|
||||
<IconComponent
|
||||
name={name}
|
||||
name={data.node?.flow ? "Ungroup" : name}
|
||||
className={
|
||||
"generic-node-icon " +
|
||||
(!showNode && "absolute inset-x-6 h-12 w-12")
|
||||
|
|
@ -161,11 +177,35 @@ export default function GenericNode({
|
|||
/>
|
||||
{showNode && (
|
||||
<div className="generic-node-tooltip-div">
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div className="generic-node-tooltip-div text-primary">
|
||||
{data.node?.display_name}
|
||||
{data.node?.flow && inputName ? (
|
||||
<div>
|
||||
<InputComponent
|
||||
autoFocus
|
||||
onBlur={() => {
|
||||
setInputName(false);
|
||||
if (nodeName.trim() !== "") {
|
||||
setNodeName(nodeName);
|
||||
data.node!.display_name = nodeName;
|
||||
} else {
|
||||
setNodeName(data.node!.display_name);
|
||||
}
|
||||
}}
|
||||
value={nodeName}
|
||||
onChange={setNodeName}
|
||||
password={false}
|
||||
blurOnEnter={true}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
) : (
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div
|
||||
className="generic-node-tooltip-div text-primary"
|
||||
onDoubleClick={() => setInputName(true)}
|
||||
>
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -179,16 +219,14 @@ export default function GenericNode({
|
|||
data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced && (
|
||||
<ParameterComponent
|
||||
key={
|
||||
(data.node!.template[
|
||||
templateField
|
||||
].input_types?.join(";") ??
|
||||
data.node!.template[templateField].type) +
|
||||
"|" +
|
||||
templateField +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
proxy: data.node!.template[templateField].proxy,
|
||||
})}
|
||||
data={data}
|
||||
setData={setData}
|
||||
color={
|
||||
|
|
@ -218,31 +256,31 @@ export default function GenericNode({
|
|||
data.node?.template[templateField].type
|
||||
}
|
||||
required={
|
||||
data.node?.template[templateField].required
|
||||
}
|
||||
id={
|
||||
(data.node?.template[
|
||||
templateField
|
||||
].input_types?.join(";") ??
|
||||
data.node?.template[templateField].type) +
|
||||
"|" +
|
||||
templateField +
|
||||
"|" +
|
||||
data.id
|
||||
data.node!.template[templateField].required
|
||||
}
|
||||
id={{
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
}}
|
||||
left={true}
|
||||
type={data.node?.template[templateField].type}
|
||||
optionalHandle={
|
||||
data.node?.template[templateField].input_types
|
||||
}
|
||||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<ParameterComponent
|
||||
key={[data.type, data.id, ...data.node!.base_classes].join(
|
||||
"|"
|
||||
)}
|
||||
key={scapedJSONStringfy({
|
||||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
})}
|
||||
data={data}
|
||||
setData={setData}
|
||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
|
|
@ -253,9 +291,11 @@ export default function GenericNode({
|
|||
: data.type
|
||||
}
|
||||
tooltipTitle={data.node?.base_classes.join("\n")}
|
||||
id={[data.type, data.id, ...data.node!.base_classes].join(
|
||||
"|"
|
||||
)}
|
||||
id={{
|
||||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
}}
|
||||
type={data.node?.base_classes.join("|")}
|
||||
left={false}
|
||||
showNode={showNode}
|
||||
|
|
@ -331,16 +371,53 @@ export default function GenericNode({
|
|||
className={
|
||||
showNode
|
||||
? "generic-node-desc " +
|
||||
(data.node?.description !== "" && showNode ? "py-5" : "pb-5")
|
||||
(data.node?.description !== "" ? "py-5" : "pb-5")
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{data.node?.description !== "" && showNode && (
|
||||
<div className="generic-node-desc-text">
|
||||
{data.node?.description !== "" &&
|
||||
showNode &&
|
||||
data.node?.flow &&
|
||||
inputDescription ? (
|
||||
<Textarea
|
||||
autoFocus
|
||||
onBlur={() => {
|
||||
setInputDescription(false);
|
||||
if (nodeDescription.trim() !== "") {
|
||||
setNodeDescription(nodeDescription);
|
||||
data.node!.description = nodeDescription;
|
||||
} else {
|
||||
setNodeDescription(data.node!.description);
|
||||
}
|
||||
}}
|
||||
value={nodeDescription}
|
||||
onChange={(e) => setNodeDescription(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, nodeDescription, "");
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
e.shiftKey === false &&
|
||||
e.ctrlKey === false &&
|
||||
e.altKey === false
|
||||
) {
|
||||
setInputDescription(false);
|
||||
if (nodeDescription.trim() !== "") {
|
||||
setNodeDescription(nodeDescription);
|
||||
data.node!.description = nodeDescription;
|
||||
} else {
|
||||
setNodeDescription(data.node!.description);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="generic-node-desc-text"
|
||||
onDoubleClick={() => setInputDescription(true)}
|
||||
>
|
||||
{data.node?.description}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<>
|
||||
{Object.keys(data.node!.template)
|
||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
||||
|
|
@ -349,15 +426,14 @@ export default function GenericNode({
|
|||
{data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
<ParameterComponent
|
||||
key={
|
||||
(data.node!.template[templateField].input_types?.join(
|
||||
";"
|
||||
) ?? data.node!.template[templateField].type) +
|
||||
"|" +
|
||||
templateField +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
proxy: data.node!.template[templateField].proxy,
|
||||
})}
|
||||
data={data}
|
||||
setData={setData}
|
||||
color={
|
||||
|
|
@ -385,21 +461,20 @@ export default function GenericNode({
|
|||
"\n"
|
||||
) ?? data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node?.template[templateField].required}
|
||||
id={
|
||||
(data.node?.template[templateField].input_types?.join(
|
||||
";"
|
||||
) ?? data.node?.template[templateField].type) +
|
||||
"|" +
|
||||
templateField +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
required={data.node!.template[templateField].required}
|
||||
id={{
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
type: data.node!.template[templateField].type,
|
||||
id: data.id,
|
||||
fieldName: templateField,
|
||||
}}
|
||||
left={true}
|
||||
type={data.node?.template[templateField].type}
|
||||
optionalHandle={
|
||||
data.node?.template[templateField].input_types
|
||||
}
|
||||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -416,7 +491,11 @@ export default function GenericNode({
|
|||
{" "}
|
||||
</div>
|
||||
<ParameterComponent
|
||||
key={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||
key={scapedJSONStringfy({
|
||||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
})}
|
||||
data={data}
|
||||
setData={setData}
|
||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
|
|
@ -426,7 +505,11 @@ export default function GenericNode({
|
|||
: data.type
|
||||
}
|
||||
tooltipTitle={data.node?.base_classes.join("\n")}
|
||||
id={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||
id={{
|
||||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
}}
|
||||
type={data.node?.base_classes.join("|")}
|
||||
left={false}
|
||||
showNode={showNode}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default function DropdownButton({
|
|||
<DropdownMenu open={showOptions}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
id="new-project-btn"
|
||||
variant="primary"
|
||||
className="relative pr-10"
|
||||
onClick={(event) => {
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
)}
|
||||
</div>
|
||||
<Input
|
||||
className="nopan nodrag noundo nocopy mt-2 font-normal"
|
||||
className="nopan nodelete nodrag noundo nocopy mt-2 font-normal"
|
||||
onChange={handleNameChange}
|
||||
type="text"
|
||||
name="name"
|
||||
value={name ?? ""}
|
||||
placeholder="File name"
|
||||
placeholder="Flow name"
|
||||
id="name"
|
||||
maxLength={maxLength}
|
||||
/>
|
||||
|
|
@ -86,7 +86,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
name="description"
|
||||
id="description"
|
||||
onChange={handleDescriptionChange}
|
||||
value={description}
|
||||
value={description!}
|
||||
placeholder="Flow description"
|
||||
className="mt-2 max-h-[100px] font-normal"
|
||||
rows={3}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ export default function BuildTrigger({
|
|||
if (isBuilding) {
|
||||
return;
|
||||
}
|
||||
const errors = validateNodes(reactFlowInstance!);
|
||||
const errors = validateNodes(
|
||||
reactFlowInstance!.getNodes(),
|
||||
reactFlowInstance!.getEdges()
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
|
|
@ -77,61 +80,54 @@ export default function BuildTrigger({
|
|||
const { flowId } = response.data;
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults: boolean[] = [];
|
||||
let finished = false;
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
// Close the connection and finish
|
||||
finished = true;
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
eventSource.close();
|
||||
resolve(validationResults.every((result) => result));
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys !== undefined) {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Otherwise, process the data
|
||||
const isValid = processStreamResult(parsedData);
|
||||
setProgress(parsedData.progress);
|
||||
validationResults.push(isValid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
setIsBuilding(false);
|
||||
}
|
||||
eventSource.close();
|
||||
|
||||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys !== undefined) {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Otherwise, process the data
|
||||
const isValid = processStreamResult(parsedData);
|
||||
setProgress(parsedData.progress);
|
||||
validationResults.push(isValid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
setIsBuilding(false);
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
finished = validationResults.length === flow.data!.nodes.length;
|
||||
}
|
||||
// Step 4: Return true if all nodes are valid, false otherwise
|
||||
return validationResults.every((result) => result);
|
||||
reject(new Error("Streaming failed"));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function processStreamResult(parsedData: parsedDataType) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default function CodeAreaComponent({
|
|||
nodeClass,
|
||||
dynamic,
|
||||
setNodeClass,
|
||||
readonly = false,
|
||||
}: CodeAreaComponentType) {
|
||||
const [myValue, setMyValue] = useState(
|
||||
typeof value == "string" ? value : JSON.stringify(value)
|
||||
|
|
@ -30,6 +31,7 @@ export default function CodeAreaComponent({
|
|||
return (
|
||||
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
||||
<CodeAreaModal
|
||||
readonly={readonly}
|
||||
dynamic={dynamic}
|
||||
value={myValue}
|
||||
nodeClass={nodeClass}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { useContext, useEffect, useState } from "react";
|
|||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import AccordionComponent from "../../components/AccordionComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||
import Dropdown from "../../components/dropdownComponent";
|
||||
import FloatComponent from "../../components/floatComponent";
|
||||
|
|
@ -31,9 +30,16 @@ import {
|
|||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { codeTabsPropsType } from "../../types/components";
|
||||
import { unselectAllNodes } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
unselectAllNodes,
|
||||
} from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import DictComponent from "../dictComponent";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import KeypairListComponent from "../keypairListComponent";
|
||||
|
||||
export default function CodeTabsComponent({
|
||||
flow,
|
||||
|
|
@ -48,6 +54,7 @@ export default function CodeTabsComponent({
|
|||
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
||||
const { dark } = useContext(darkContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (flow && flow["data"]!["nodes"]) {
|
||||
|
|
@ -56,7 +63,7 @@ export default function CodeTabsComponent({
|
|||
}, [flow]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tweaks) {
|
||||
if (tweaks && data) {
|
||||
unselectAllNodes({
|
||||
data,
|
||||
updateNodes: (nodes) => {
|
||||
|
|
@ -248,7 +255,11 @@ export default function CodeTabsComponent({
|
|||
node.data.node.template[templateField]
|
||||
.type === "file" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "int")
|
||||
.type === "int" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "dict" ||
|
||||
node.data.node.template[templateField]
|
||||
.type === "NestedDict")
|
||||
)
|
||||
.map((templateField, indx) => {
|
||||
return (
|
||||
|
|
@ -310,55 +321,46 @@ export default function CodeTabsComponent({
|
|||
) : node.data.node.template[
|
||||
templateField
|
||||
].multiline ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent!(
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<TextAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
!node.data.node
|
||||
.template[
|
||||
<div>
|
||||
<TextAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
!node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node
|
||||
.template[
|
||||
templateField
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<InputComponent
|
||||
editNode={true}
|
||||
|
|
@ -440,52 +442,35 @@ export default function CodeTabsComponent({
|
|||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "file" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent!(
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(
|
||||
target: any
|
||||
) => {}}
|
||||
fileTypes={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].fileTypes
|
||||
}
|
||||
suffixes={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].suffixes
|
||||
}
|
||||
onFileChange={(
|
||||
value: any
|
||||
) => {
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].file_path = value;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(target: any) => {}}
|
||||
fileTypes={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].fileTypes
|
||||
}
|
||||
suffixes={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].suffixes
|
||||
}
|
||||
onFileChange={(
|
||||
value: any
|
||||
) => {
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].file_path = value;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "float" ? (
|
||||
|
|
@ -617,114 +602,197 @@ export default function CodeTabsComponent({
|
|||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "prompt" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent!(
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
readonly={
|
||||
node.data.node?.flow &&
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node.template[
|
||||
? ""
|
||||
: node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "code" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent!(
|
||||
tweaks.getValue!(
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
readonly={
|
||||
node.data.node?.flow &&
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value,
|
||||
node.data,
|
||||
].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
]
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "dict" ? (
|
||||
<div className="mx-auto overflow-auto custom-scroll">
|
||||
<KeypairListComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
node.data.node!.template[
|
||||
templateField
|
||||
].value?.length === 0 ||
|
||||
!node.data.node!.template[
|
||||
templateField
|
||||
].value
|
||||
? [{ "": "" }]
|
||||
: convertObjToArray(
|
||||
node.data.node!
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
]
|
||||
)
|
||||
}
|
||||
duplicateKey={
|
||||
errorDuplicateKey
|
||||
}
|
||||
onChange={(target) => {
|
||||
const valueToNumbers =
|
||||
convertValuesToNumbers(
|
||||
target
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
node.data.node!.template[
|
||||
templateField
|
||||
].value = valueToNumbers;
|
||||
setErrorDuplicateKey(
|
||||
hasDuplicateKeys(
|
||||
valueToNumbers
|
||||
)
|
||||
);
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "NestedDict" ? (
|
||||
<div className="mx-auto">
|
||||
<DictComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
node.data.node!.template[
|
||||
templateField
|
||||
].value.toString() === "{}"
|
||||
? {
|
||||
yourkey: "value",
|
||||
}
|
||||
: node.data.node!
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "Any" ? (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { DictComponentType } from "../../types/components";
|
||||
|
||||
import DictAreaModal from "../../modals/dictAreaModal";
|
||||
|
|
@ -13,7 +13,7 @@ export default function DictComponent({
|
|||
}: DictComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
onChange([""]);
|
||||
onChange({});
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
|
|
@ -21,6 +21,8 @@ export default function DictComponent({
|
|||
if (value) onChange(value);
|
||||
}, [value]);
|
||||
|
||||
const ref = useRef(value);
|
||||
debugger;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
|
|
@ -31,7 +33,7 @@ export default function DictComponent({
|
|||
{
|
||||
<div className="flex w-full gap-3">
|
||||
<DictAreaModal
|
||||
value={value}
|
||||
value={ref.current}
|
||||
onChange={(obj) => {
|
||||
onChange(obj);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default function FloatComponent({
|
|||
return (
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="float-input"
|
||||
type="number"
|
||||
step={step}
|
||||
min={min}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
|||
|
||||
function handleAddFlow() {
|
||||
try {
|
||||
addFlow(undefined, true).then((id) => {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
// saveFlowStyleInDataBase();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { InputComponentType } from "../../types/components";
|
||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function InputComponent({
|
||||
autoFocus = false,
|
||||
onBlur,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
|
|
@ -15,9 +17,10 @@ export default function InputComponent({
|
|||
editNode = false,
|
||||
placeholder = "Type something...",
|
||||
className,
|
||||
blurOnEnter = false,
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
// Clear component state
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
@ -30,6 +33,9 @@ export default function InputComponent({
|
|||
{isForm ? (
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
ref={refInput}
|
||||
onBlur={onBlur}
|
||||
autoFocus={autoFocus}
|
||||
type={password && !pwdVisible ? "password" : "text"}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
|
|
@ -49,13 +55,17 @@ export default function InputComponent({
|
|||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
<Input
|
||||
ref={refInput}
|
||||
type="text"
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={classNames(
|
||||
|
|
@ -73,6 +83,7 @@ export default function InputComponent({
|
|||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { KeyPairListComponentType } from "../../types/components";
|
||||
|
||||
import _ from "lodash";
|
||||
|
|
@ -12,6 +12,8 @@ export default function KeypairListComponent({
|
|||
disabled,
|
||||
editNode = false,
|
||||
duplicateKey,
|
||||
advanced = false,
|
||||
dataValue,
|
||||
}: KeyPairListComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
@ -19,37 +21,41 @@ export default function KeypairListComponent({
|
|||
}
|
||||
}, [disabled]);
|
||||
|
||||
const ref = useRef(value.length === 0 ? [{ "": "" }] : value);
|
||||
|
||||
useEffect(() => {
|
||||
if (JSON.stringify(value) !== JSON.stringify(ref.current)) {
|
||||
ref.current = value;
|
||||
onChange(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleChangeKey = (event, idx) => {
|
||||
const newInputList = _.cloneDeep(value);
|
||||
const oldKey = Object.keys(newInputList[idx])[0];
|
||||
const updatedObj = { [event.target.value]: newInputList[idx][oldKey] };
|
||||
newInputList[idx] = updatedObj;
|
||||
onChange(newInputList);
|
||||
const oldKey = Object.keys(ref.current[idx])[0];
|
||||
const updatedObj = { [event.target.value]: ref.current[idx][oldKey] };
|
||||
ref.current[idx] = updatedObj;
|
||||
onChange(ref.current);
|
||||
};
|
||||
|
||||
const handleChangeValue = (newValue, idx) => {
|
||||
const newInputList = _.cloneDeep(value);
|
||||
const key = Object.keys(newInputList[idx])[0];
|
||||
newInputList[idx][key] = newValue;
|
||||
onChange(newInputList);
|
||||
const key = Object.keys(ref.current[idx])[0];
|
||||
ref.current[idx][key] = newValue;
|
||||
onChange(ref.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value) onChange(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
value?.length > 1 && editNode ? "my-1" : "",
|
||||
"flex flex-col gap-3"
|
||||
ref.current?.length > 1 && editNode ? "mx-2 my-1" : "",
|
||||
"flex h-full flex-col gap-3"
|
||||
)}
|
||||
>
|
||||
{value?.map((obj, index) => {
|
||||
{ref.current?.map((obj, index) => {
|
||||
return Object.keys(obj).map((key, idx) => {
|
||||
return (
|
||||
<div key={idx} className="flex w-full gap-3">
|
||||
<div key={idx} className="flex w-full gap-2">
|
||||
<Input
|
||||
id={"keypair" + index}
|
||||
type="text"
|
||||
value={key.trim()}
|
||||
className={classNames(
|
||||
|
|
@ -67,6 +73,7 @@ export default function KeypairListComponent({
|
|||
/>
|
||||
|
||||
<Input
|
||||
id={"keypair" + (index + 100).toString()}
|
||||
type="text"
|
||||
value={obj[key]}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
|
|
@ -76,13 +83,14 @@ export default function KeypairListComponent({
|
|||
}
|
||||
/>
|
||||
|
||||
{index === value.length - 1 ? (
|
||||
{index === ref.current.length - 1 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
let newInputList = _.cloneDeep(value);
|
||||
let newInputList = _.cloneDeep(ref.current);
|
||||
newInputList.push({ "": "" });
|
||||
onChange(newInputList);
|
||||
}}
|
||||
id={"plusbtn" + index.toString()}
|
||||
>
|
||||
<IconComponent
|
||||
name="Plus"
|
||||
|
|
@ -92,10 +100,11 @@ export default function KeypairListComponent({
|
|||
) : (
|
||||
<button
|
||||
onClick={() => {
|
||||
let newInputList = _.cloneDeep(value);
|
||||
let newInputList = _.cloneDeep(ref.current);
|
||||
newInputList.splice(index, 1);
|
||||
onChange(newInputList);
|
||||
}}
|
||||
id={"minusbtn" + index.toString()}
|
||||
>
|
||||
<IconComponent
|
||||
name="X"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export default function PromptAreaComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: PromptAreaComponentType) {
|
||||
readonly = false,
|
||||
}: PromptAreaComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
onChange("");
|
||||
|
|
@ -22,7 +23,7 @@ export default function PromptAreaComponent({
|
|||
}, [disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== "" && !editNode) {
|
||||
if (value !== "" && !editNode && !readonly) {
|
||||
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
|
||||
if (apiReturn.data) {
|
||||
setNodeClass!(apiReturn.data.frontend_node);
|
||||
|
|
@ -35,6 +36,7 @@ export default function PromptAreaComponent({
|
|||
return (
|
||||
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
||||
<GenericModal
|
||||
readonly={readonly}
|
||||
type={TypeModal.PROMPT}
|
||||
value={value}
|
||||
buttonText="Check & Save"
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ export const SkeletonCardComponent = (): JSX.Element => {
|
|||
<div className="skeleton-card">
|
||||
<div className="skeleton-card-wrapper">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<Skeleton className="h-4 w-[120px]" />
|
||||
<Skeleton className="h-4 w-[40%]" />
|
||||
</div>
|
||||
<div className="skeleton-card-text">
|
||||
<Skeleton className="h-3 w-[250px]" />
|
||||
<Skeleton className="h-3 w-[200px]" />
|
||||
<Skeleton className="h-3 w-[90%]" />
|
||||
<Skeleton className="h-3 w-[80%]" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export default function ToggleShadComponent({
|
|||
setEnabled,
|
||||
disabled,
|
||||
size,
|
||||
id = "",
|
||||
}: ToggleComponentType): JSX.Element {
|
||||
let scaleX, scaleY;
|
||||
switch (size) {
|
||||
|
|
@ -29,6 +30,7 @@ export default function ToggleShadComponent({
|
|||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
|
||||
<Switch
|
||||
id={id}
|
||||
style={{
|
||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@ const Dialog = DialogPrimitive.Root;
|
|||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className="nopan nodrag noundo nocopy fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
<DialogPrimitive.Portal {...props}>
|
||||
<div className="nopan nodelete nodrag noundo nocopy fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</DialogPrimitive.Portal>
|
||||
|
|
@ -27,7 +26,7 @@ const DialogOverlay = React.forwardRef<
|
|||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"nopan nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"nopan nodelete nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn("nopan nodrag noundo nocopy primary-input", className)}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noundo nocopy primary-input",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export default function RenameLabel(props) {
|
|||
ref={inputRef}
|
||||
onInput={resizeInput}
|
||||
className={cn(
|
||||
"nopan nodrag noundo nocopy rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
|
||||
"nopan nodelete nodrag noundo nocopy rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
|
||||
props.className
|
||||
)}
|
||||
onBlur={() => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn("nopan nodrag noundo nocopy textarea-primary", className)}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noundo nocopy textarea-primary",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
|
|||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"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",
|
||||
"z-50 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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -529,7 +529,13 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
|||
"http://localhost:7860/login",
|
||||
];
|
||||
|
||||
export const skipNodeUpdate = ["CustomComponent"];
|
||||
export const skipNodeUpdate = [
|
||||
"CustomComponent",
|
||||
"PromptTemplate",
|
||||
"ChatMessagePromptTemplate",
|
||||
"SystemMessagePromptTemplate",
|
||||
"HumanMessagePromptTemplate",
|
||||
];
|
||||
|
||||
export const CONTROL_INPUT_STATE = {
|
||||
password: "",
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export const AuthContext = createContext<AuthContextType>(initialValue);
|
|||
export function AuthProvider({ children }): React.ReactElement {
|
||||
const cookies = new Cookies();
|
||||
const [accessToken, setAccessToken] = useState<string | null>(
|
||||
cookies.get("access_token")
|
||||
cookies.get("access_tkn_lflw")
|
||||
);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(
|
||||
cookies.get("refresh_token")
|
||||
cookies.get("refresh_tkn_lflw")
|
||||
);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
|
|
@ -37,7 +37,7 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||
const { setLoading } = useContext(alertContext);
|
||||
useEffect(() => {
|
||||
const storedAccessToken = cookies.get("access_token");
|
||||
const storedAccessToken = cookies.get("access_tkn_lflw");
|
||||
if (storedAccessToken) {
|
||||
setAccessToken(storedAccessToken);
|
||||
}
|
||||
|
|
@ -66,7 +66,10 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
const isSuperUser = user!.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
})
|
||||
.catch((error) => {});
|
||||
.catch((error) => {
|
||||
console.log("auth context");
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -74,23 +77,23 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
|
||||
|
||||
function getAuthentication() {
|
||||
const storedRefreshToken = cookies.get("refresh_token");
|
||||
const storedAccess = cookies.get("access_token");
|
||||
const storedRefreshToken = cookies.get("refresh_tkn_lflw");
|
||||
const storedAccess = cookies.get("access_tkn_lflw");
|
||||
const auth = storedAccess && storedRefreshToken ? true : false;
|
||||
return auth;
|
||||
}
|
||||
|
||||
function login(newAccessToken: string, refreshToken: string) {
|
||||
cookies.set("access_token", newAccessToken, { path: "/" });
|
||||
cookies.set("refresh_token", refreshToken, { path: "/" });
|
||||
cookies.set("access_tkn_lflw", newAccessToken, { path: "/" });
|
||||
cookies.set("refresh_tkn_lflw", refreshToken, { path: "/" });
|
||||
setAccessToken(newAccessToken);
|
||||
setRefreshToken(refreshToken);
|
||||
setIsAuthenticated(true);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
cookies.remove("access_token", { path: "/" });
|
||||
cookies.remove("refresh_token", { path: "/" });
|
||||
cookies.remove("access_tkn_lflw", { path: "/" });
|
||||
cookies.remove("refresh_tkn_lflw", { path: "/" });
|
||||
setIsAdmin(false);
|
||||
setUserData(null);
|
||||
setAccessToken(null);
|
||||
|
|
|
|||
|
|
@ -21,10 +21,20 @@ import {
|
|||
} from "../controllers/API";
|
||||
import { APIClassType, APITemplateType } from "../types/api";
|
||||
import { tweakType } from "../types/components";
|
||||
import { FlowType, NodeDataType, NodeType } from "../types/flow";
|
||||
import {
|
||||
FlowType,
|
||||
NodeDataType,
|
||||
NodeType,
|
||||
sourceHandleType,
|
||||
targetHandleType,
|
||||
} from "../types/flow";
|
||||
import { TabsContextType, TabsState } from "../types/tabs";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
checkOldEdgesHandles,
|
||||
scapeJSONParse,
|
||||
scapedJSONStringfy,
|
||||
updateEdgesHandleIds,
|
||||
updateIds,
|
||||
updateTemplate,
|
||||
} from "../utils/reactflowUtils";
|
||||
|
|
@ -36,13 +46,12 @@ import { typesContext } from "./typesContext";
|
|||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
||||
const TabsContextInitialValue: TabsContextType = {
|
||||
save: () => {},
|
||||
tabId: "",
|
||||
setTabId: (index: string) => {},
|
||||
isLoading: true,
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: async (flowData?: any) => "",
|
||||
addFlow: async (newProject: boolean, flowData?: FlowType) => "",
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => uid(),
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
|
|
@ -52,7 +61,7 @@ const TabsContextInitialValue: TabsContextType = {
|
|||
isBuilt: false,
|
||||
setIsBuilt: (state: boolean) => {},
|
||||
hardReset: () => {},
|
||||
saveFlow: async (flow: FlowType) => {},
|
||||
saveFlow: async (flow: FlowType, silent?: boolean) => {},
|
||||
lastCopiedSelection: null,
|
||||
setLastCopiedSelection: (selection: any) => {},
|
||||
tabsState: {},
|
||||
|
|
@ -101,29 +110,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return newNodeId.current;
|
||||
}
|
||||
|
||||
function save() {
|
||||
// added clone deep to avoid mutating the original object
|
||||
let Saveflows = _.cloneDeep(flows);
|
||||
if (Saveflows.length !== 0) {
|
||||
Saveflows.forEach((flow) => {
|
||||
if (flow.data && flow.data?.nodes)
|
||||
flow.data?.nodes.forEach((node) => {
|
||||
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
|
||||
Object.keys(node.data.node.template).forEach((key) => {
|
||||
if (node.data.node.template[key].type === "file") {
|
||||
node.data.node.template[key].content = null;
|
||||
node.data.node.template[key].value = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabId, flows: Saveflows, id })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshFlows() {
|
||||
setIsLoading(true);
|
||||
getTabsDataFromDB().then((DbData) => {
|
||||
|
|
@ -158,14 +144,18 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
if (!flow.data) {
|
||||
return;
|
||||
}
|
||||
processFlowEdges(flow);
|
||||
processFlowNodes(flow);
|
||||
processDataFromFlow(flow, false);
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
function processFlowEdges(flow: FlowType) {
|
||||
if (!flow.data || !flow.data.edges) return;
|
||||
if (checkOldEdgesHandles(flow.data.edges)) {
|
||||
const newEdges = updateEdgesHandleIds(flow.data);
|
||||
flow.data.edges = newEdges;
|
||||
}
|
||||
//update edges colors
|
||||
flow.data.edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
edge.style = { stroke: "#555" };
|
||||
|
|
@ -183,6 +173,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
function processFlowNodes(flow: FlowType) {
|
||||
if (!flow.data || !flow.data.nodes) return;
|
||||
flow.data.nodes.forEach((node: NodeType) => {
|
||||
if (node.data.node?.flow) return;
|
||||
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
|
|
@ -192,6 +183,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
if (Object.keys(template["template"]).length > 0) {
|
||||
updateDisplay_name(node, template);
|
||||
updateNodeBaseClasses(node, template);
|
||||
//update baseclasses in edges
|
||||
updateNodeEdges(flow, node, template);
|
||||
updateNodeDescription(node, template);
|
||||
updateNodeTemplate(node, template);
|
||||
|
|
@ -211,11 +203,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
) {
|
||||
flow.data!.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
?.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
let sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!
|
||||
);
|
||||
sourceHandleObject.baseClasses = template["base_classes"];
|
||||
edge.data.sourceHandle = sourceHandleObject;
|
||||
edge.sourceHandle = scapedJSONStringfy(sourceHandleObject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -298,16 +291,22 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
* The resulting JSON object is passed to the addFlow function.
|
||||
*/
|
||||
async function uploadFlow(
|
||||
newProject?: boolean,
|
||||
newProject: boolean,
|
||||
file?: File
|
||||
): Promise<String | undefined> {
|
||||
let id;
|
||||
if (file) {
|
||||
let text = await file.text();
|
||||
let fileData = JSON.parse(text);
|
||||
if (fileData.flows) {
|
||||
fileData.flows.forEach((flow: FlowType) => {
|
||||
id = addFlow(flow, newProject);
|
||||
});
|
||||
}
|
||||
// parse the text into a JSON object
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
|
||||
id = await addFlow(flow, newProject);
|
||||
id = await addFlow(newProject, flow);
|
||||
} else {
|
||||
// create a file input
|
||||
const input = document.createElement("input");
|
||||
|
|
@ -322,7 +321,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const currentfile = (e.target as HTMLInputElement).files![0];
|
||||
let text = await currentfile.text();
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
const flowId = await addFlow(flow, newProject);
|
||||
const flowId = await addFlow(newProject, flow);
|
||||
resolve(flowId);
|
||||
}
|
||||
};
|
||||
|
|
@ -373,7 +372,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
* Add a new flow to the list of flows.
|
||||
* @param flow Optional flow to add.
|
||||
*/
|
||||
|
||||
function paste(
|
||||
selectionInstance: { nodes: Node[]; edges: Edge[] },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
|
|
@ -422,19 +420,27 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
reactFlowInstance!.setNodes(nodes);
|
||||
|
||||
selectionInstance.edges.forEach((edge) => {
|
||||
selectionInstance.edges.forEach((edge: Edge) => {
|
||||
let source = idsMap[edge.source];
|
||||
let target = idsMap[edge.target];
|
||||
let sourceHandleSplitted = edge.sourceHandle!.split("|");
|
||||
let sourceHandle =
|
||||
sourceHandleSplitted[0] +
|
||||
"|" +
|
||||
source +
|
||||
"|" +
|
||||
sourceHandleSplitted.slice(2).join("|");
|
||||
let targetHandleSplitted = edge.targetHandle!.split("|");
|
||||
let targetHandle =
|
||||
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!
|
||||
);
|
||||
let sourceHandle = scapedJSONStringfy({
|
||||
...sourceHandleObject,
|
||||
id: source,
|
||||
});
|
||||
sourceHandleObject.id = source;
|
||||
edge.data.sourceHandle = sourceHandleObject;
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!
|
||||
);
|
||||
let targetHandle = scapedJSONStringfy({
|
||||
...targetHandleObject,
|
||||
id: target,
|
||||
});
|
||||
targetHandleObject.id = target;
|
||||
edge.data.targetHandle = targetHandleObject;
|
||||
let id =
|
||||
"reactflow__edge-" +
|
||||
source +
|
||||
|
|
@ -451,10 +457,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
id,
|
||||
style: { stroke: "#555" },
|
||||
className:
|
||||
targetHandle.split("|")[0] === "Text"
|
||||
targetHandleObject.type === "Text"
|
||||
? "stroke-gray-800 "
|
||||
: "stroke-gray-900 ",
|
||||
animated: targetHandle.split("|")[0] === "Text",
|
||||
animated: targetHandleObject.type === "Text",
|
||||
selected: false,
|
||||
},
|
||||
edges.map((edge) => ({ ...edge, selected: false }))
|
||||
|
|
@ -464,19 +470,16 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
const addFlow = async (
|
||||
flow?: FlowType,
|
||||
newProject?: Boolean
|
||||
newProject: Boolean,
|
||||
flow?: FlowType
|
||||
): Promise<String | undefined> => {
|
||||
if (newProject) {
|
||||
let flowData = extractDataFromFlow(flow!);
|
||||
if (flowData.description == "") {
|
||||
flowData.description = getRandomDescription();
|
||||
}
|
||||
let flowData = flow
|
||||
? processDataFromFlow(flow)
|
||||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const newFlow = createNewFlow(flowData, flow!);
|
||||
processFlowEdges(newFlow);
|
||||
processFlowNodes(newFlow);
|
||||
|
||||
const flowName = addVersionToDuplicates(newFlow, flows);
|
||||
|
||||
|
|
@ -504,31 +507,36 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
};
|
||||
|
||||
const extractDataFromFlow = (flow: FlowType) => {
|
||||
const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
|
||||
let data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
|
||||
if (data) {
|
||||
processFlowEdges(flow);
|
||||
processFlowNodes(flow);
|
||||
//add animation to text type edges
|
||||
updateEdges(data.edges);
|
||||
updateNodes(data.nodes, data.edges);
|
||||
updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
|
||||
// updateNodes(data.nodes, data.edges);
|
||||
if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
|
||||
}
|
||||
|
||||
return { data, description };
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateEdges = (edges: Edge[]) => {
|
||||
edges.forEach((edge) => {
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!
|
||||
);
|
||||
edge.className =
|
||||
(edge.targetHandle!.split("|")[0] === "Text"
|
||||
(targetHandleObject.type === "Text"
|
||||
? "stroke-gray-800 "
|
||||
: "stroke-gray-900 ") + " stroke-connection";
|
||||
edge.animated = edge.targetHandle!.split("|")[0] === "Text";
|
||||
edge.animated = targetHandleObject.type === "Text";
|
||||
});
|
||||
};
|
||||
|
||||
const updateNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.data.node?.flow) return;
|
||||
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
|
|
@ -538,12 +546,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
if (Object.keys(template["template"]).length > 0) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
edges.forEach((edge) => {
|
||||
let sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!
|
||||
);
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge
|
||||
.sourceHandle!.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
let newSourceHandle = sourceHandleObject;
|
||||
newSourceHandle.baseClasses.concat(template["base_classes"]);
|
||||
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
}
|
||||
});
|
||||
node.data.node.description = template["description"];
|
||||
|
|
@ -556,12 +565,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
};
|
||||
|
||||
const createNewFlow = (
|
||||
flowData: { data: ReactFlowJsonObject | null; description: string },
|
||||
flowData: ReactFlowJsonObject | null,
|
||||
flow: FlowType
|
||||
) => ({
|
||||
description: flowData.description,
|
||||
description: flow?.description ?? getRandomDescription(),
|
||||
name: flow?.name ?? getRandomName(),
|
||||
data: flowData.data,
|
||||
data: flowData,
|
||||
id: "",
|
||||
});
|
||||
|
||||
|
|
@ -588,13 +597,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
|
||||
async function saveFlow(newFlow: FlowType) {
|
||||
async function saveFlow(newFlow: FlowType, silent?: boolean) {
|
||||
try {
|
||||
// updates flow in db
|
||||
const updatedFlow = await updateFlowInDatabase(newFlow);
|
||||
if (updatedFlow) {
|
||||
// updates flow in state
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
if (!silent) {
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
}
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
|
|
@ -638,7 +649,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
tabId,
|
||||
setTabId,
|
||||
flows,
|
||||
save,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Node, ReactFlowInstance } from "reactflow";
|
||||
import { Edge, Node, ReactFlowInstance } from "reactflow";
|
||||
import { getAll, getHealth } from "../controllers/API";
|
||||
import { APIKindType } from "../types/api";
|
||||
import { localStorageUserType } from "../types/entities";
|
||||
|
|
@ -38,6 +38,7 @@ const initialValue: typesContextType = {
|
|||
getFilterEdge: [],
|
||||
saveComponent: (component: NodeDataType, key: string) => {},
|
||||
deleteComponent: (id: string, key: string) => {},
|
||||
deleteEdge: () => {},
|
||||
};
|
||||
|
||||
export const typesContext = createContext<typesContextType>(initialValue);
|
||||
|
|
@ -115,14 +116,31 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
}
|
||||
|
||||
function deleteNode(idx: string) {
|
||||
function deleteNode(idx: string | Array<string>) {
|
||||
reactFlowInstance!.setNodes(
|
||||
reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
|
||||
reactFlowInstance!
|
||||
.getNodes()
|
||||
.filter((node: Node) =>
|
||||
typeof idx === "string" ? node.id !== idx : !idx.includes(node.id)
|
||||
)
|
||||
);
|
||||
reactFlowInstance!.setEdges(
|
||||
reactFlowInstance!
|
||||
.getEdges()
|
||||
.filter((edge) => edge.source !== idx && edge.target !== idx)
|
||||
.filter((edge) =>
|
||||
typeof idx === "string"
|
||||
? edge.source !== idx && edge.target !== idx
|
||||
: !idx.includes(edge.source) && !idx.includes(edge.target)
|
||||
)
|
||||
);
|
||||
}
|
||||
function deleteEdge(idx: string | Array<string>) {
|
||||
reactFlowInstance!.setEdges(
|
||||
reactFlowInstance!
|
||||
.getEdges()
|
||||
.filter((edge: Edge) =>
|
||||
typeof idx === "string" ? edge.id !== idx : !idx.includes(edge.id)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +199,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
return (
|
||||
<typesContext.Provider
|
||||
value={{
|
||||
deleteEdge,
|
||||
deleteComponent,
|
||||
saveComponent,
|
||||
types,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function ApiInterceptor() {
|
|||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
if (error.response?.status === 401) {
|
||||
const refreshToken = cookies.get("refresh_token");
|
||||
const refreshToken = cookies.get("refresh_tkn_lflw");
|
||||
if (refreshToken && refreshToken !== "auto") {
|
||||
authenticationErrorCount = authenticationErrorCount + 1;
|
||||
if (authenticationErrorCount > 3) {
|
||||
|
|
@ -31,17 +31,15 @@ function ApiInterceptor() {
|
|||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
const res = await renewAccessToken(refreshToken);
|
||||
if (res?.data?.access_token && res?.data?.refresh_token) {
|
||||
login(res?.data?.access_token, res?.data?.refresh_token);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await renewAccessToken(refreshToken);
|
||||
if (res?.data?.access_token && res?.data?.refresh_token) {
|
||||
login(res?.data?.access_token, res?.data?.refresh_token);
|
||||
}
|
||||
if (error?.config?.headers) {
|
||||
delete error.config.headers["Authorization"];
|
||||
error.config.headers["Authorization"] = `Bearer ${cookies.get(
|
||||
"access_token"
|
||||
"access_tkn_lflw"
|
||||
)}`;
|
||||
const response = await axios.request(error.config);
|
||||
return response;
|
||||
|
|
@ -50,6 +48,10 @@ function ApiInterceptor() {
|
|||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
logout();
|
||||
navigate("/login");
|
||||
} else {
|
||||
console.error(error);
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ export async function updateFlowInDatabase(
|
|||
description: updatedFlow.description,
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response?.status}`);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
|
|
@ -399,7 +399,6 @@ export async function renewAccessToken(token: string) {
|
|||
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { TabsContext } from "../../contexts/tabsContext";
|
|||
import { TemplateVariableType } from "../../types/api";
|
||||
import { tweakType, uniqueTweakType } from "../../types/components";
|
||||
import { FlowType, NodeType } from "../../types/flow/index";
|
||||
import { buildTweaks } from "../../utils/reactflowUtils";
|
||||
import { buildTweaks, convertArrayToObj } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
getCurlCode,
|
||||
getPythonApiCode,
|
||||
|
|
@ -105,7 +105,9 @@ const ApiModal = forwardRef(
|
|||
node.data.node.template[templateField].type === "code" ||
|
||||
node.data.node.template[templateField].type === "prompt" ||
|
||||
node.data.node.template[templateField].type === "file" ||
|
||||
node.data.node.template[templateField].type === "int")
|
||||
node.data.node.template[templateField].type === "int" ||
|
||||
node.data.node.template[templateField].type === "dict" ||
|
||||
node.data.node.template[templateField].type === "NestedDict")
|
||||
)
|
||||
.map((n, i) => {
|
||||
arrNodesWithValues.push(node["id"]);
|
||||
|
|
@ -118,7 +120,7 @@ const ApiModal = forwardRef(
|
|||
}
|
||||
function buildTweakObject(
|
||||
tw: string,
|
||||
changes: string | string[] | boolean | number,
|
||||
changes: string | string[] | boolean | number | Object[] | Object,
|
||||
template: TemplateVariableType
|
||||
) {
|
||||
if (typeof changes === "string" && template.type === "float") {
|
||||
|
|
@ -131,6 +133,14 @@ const ApiModal = forwardRef(
|
|||
changes = changes?.filter((x) => x !== "");
|
||||
}
|
||||
|
||||
if (template.type === "dict" && Array.isArray(changes)) {
|
||||
changes = convertArrayToObj(changes);
|
||||
}
|
||||
|
||||
if (template.type === "NestedDict") {
|
||||
changes = JSON.stringify(changes);
|
||||
}
|
||||
|
||||
const existingTweak = tweak.current.find((element) =>
|
||||
element.hasOwnProperty(tw)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
ReactNode,
|
||||
forwardRef,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||
import DictComponent from "../../components/dictComponent";
|
||||
import Dropdown from "../../components/dropdownComponent";
|
||||
|
|
@ -30,6 +38,7 @@ import { NodeDataType } from "../../types/flow";
|
|||
import { TabsState } from "../../types/tabs";
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
} from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
|
|
@ -55,7 +64,9 @@ const EditNodeModal = forwardRef(
|
|||
ref
|
||||
) => {
|
||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||
const [myData, setMyData] = useState(data);
|
||||
|
||||
const myData = useRef(data);
|
||||
|
||||
const { setTabsState, tabId } = useContext(TabsContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
let disabled =
|
||||
|
|
@ -64,55 +75,39 @@ const EditNodeModal = forwardRef(
|
|||
.some((edge) => edge.targetHandle === data.id) ?? false;
|
||||
|
||||
function changeAdvanced(n) {
|
||||
let newData = cloneDeep(data);
|
||||
newData.node!.template[n].advanced = !newData.node!.template[n].advanced;
|
||||
setMyData(newData);
|
||||
myData.current.node!.template[n].advanced =
|
||||
!myData.current.node!.template[n].advanced;
|
||||
setAdv(!adv);
|
||||
}
|
||||
|
||||
const handleOnNewValue = (newValue: any, name) => {
|
||||
let newData = cloneDeep(data);
|
||||
newData.node!.template[name].value = newValue;
|
||||
setMyData(newData);
|
||||
myData.current.node!.template[name].value = newValue;
|
||||
setDataValue(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMyData(data); // reset data to what it is on node when opening modal
|
||||
myData.current = data; // reset data to what it is on node when opening modal
|
||||
onClose!(modalOpen);
|
||||
}, [modalOpen]);
|
||||
|
||||
const [obj, setObj] = useState({
|
||||
arr: ["test", 123456, false, null],
|
||||
boolean: false,
|
||||
longString:
|
||||
"long string long string long string long string long string long string",
|
||||
number: 123456,
|
||||
try: {
|
||||
k1: 123,
|
||||
k2: "123",
|
||||
k3: false,
|
||||
},
|
||||
string: "string",
|
||||
});
|
||||
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
const [dictArr, setDictArr] = useState([
|
||||
{ yourKey: "yourValue" },
|
||||
] as Object[]);
|
||||
const [adv, setAdv] = useState<boolean | null>(null);
|
||||
const [dataValue, setDataValue] = useState(data);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
key={data.id}
|
||||
size="large-h-full"
|
||||
open={modalOpen}
|
||||
setOpen={setModalOpen}
|
||||
onChangeOpenModal={(open) => {
|
||||
let newData = cloneDeep(data);
|
||||
setMyData(newData);
|
||||
myData.current = data;
|
||||
}}
|
||||
>
|
||||
<BaseModal.Trigger>{children}</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={myData.current.node?.description!}>
|
||||
<span className="pr-2">{myData.current.type}</span>
|
||||
<Badge variant="secondary">ID: {myData.current.id}</Badge>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<div className="flex pb-2">
|
||||
|
|
@ -145,110 +140,92 @@ const EditNodeModal = forwardRef(
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="p-0">
|
||||
{Object.keys(myData.node!.template)
|
||||
{Object.keys(myData.current.node!.template)
|
||||
.filter(
|
||||
(templateParam) =>
|
||||
templateParam.charAt(0) !== "_" &&
|
||||
myData.node?.template[templateParam].show &&
|
||||
(myData.node.template[templateParam].type ===
|
||||
"str" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"bool" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"float" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"code" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"prompt" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"file" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
"int")
|
||||
myData.current.node?.template[templateParam].show &&
|
||||
(myData.current.node.template[templateParam]
|
||||
.type === "str" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "bool" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "float" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "code" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "prompt" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "file" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "int" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "dict" ||
|
||||
myData.current.node.template[templateParam]
|
||||
.type === "NestedDict")
|
||||
)
|
||||
.map((templateParam, index) => (
|
||||
<TableRow key={index} className="h-10">
|
||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
||||
{myData.node?.template[templateParam].name
|
||||
? myData.node.template[templateParam].name
|
||||
: myData.node?.template[templateParam]
|
||||
.display_name}
|
||||
<ShadTooltip
|
||||
content={
|
||||
myData.current.node?.template[templateParam]
|
||||
.proxy
|
||||
? myData.current.node?.template[
|
||||
templateParam
|
||||
].proxy?.id
|
||||
: null
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{myData.current.node?.template[templateParam]
|
||||
.display_name
|
||||
? myData.current.node.template[
|
||||
templateParam
|
||||
].display_name
|
||||
: myData.current.node?.template[
|
||||
templateParam
|
||||
].name}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
||||
{myData.node?.template[templateParam].type ===
|
||||
"str" &&
|
||||
!myData.node.template[templateParam].options ? (
|
||||
{myData.current.node?.template[templateParam]
|
||||
.type === "str" &&
|
||||
!myData.current.node.template[templateParam]
|
||||
.options ? (
|
||||
<div className="mx-auto">
|
||||
{myData.node.template[templateParam].list ? (
|
||||
{myData.current.node.template[templateParam]
|
||||
.list ? (
|
||||
<InputListComponent
|
||||
editNode={true}
|
||||
disabled={disabled}
|
||||
value={
|
||||
!myData.node.template[templateParam]
|
||||
.value ||
|
||||
myData.node.template[templateParam]
|
||||
.value === ""
|
||||
!myData.current.node.template[
|
||||
templateParam
|
||||
].value ||
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value === ""
|
||||
? [""]
|
||||
: myData.node.template[templateParam]
|
||||
.value
|
||||
: myData.current.node.template[
|
||||
templateParam
|
||||
].value
|
||||
}
|
||||
onChange={(value: string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "NestedDict" ? (
|
||||
<div className="mt-2 w-full">
|
||||
<DictComponent
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={obj}
|
||||
onChange={(newValue) => {
|
||||
setObj(newValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "dict" ? (
|
||||
<div className="mt-2 w-full">
|
||||
<KeypairListComponent
|
||||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value?.length === 0 ||
|
||||
!myData.node.template[templateParam]
|
||||
.value
|
||||
? dictArr
|
||||
: convertObjToArray(
|
||||
myData.node.template[
|
||||
templateParam
|
||||
].value
|
||||
)
|
||||
}
|
||||
duplicateKey={errorDuplicateKey}
|
||||
onChange={(newValue) => {
|
||||
setErrorDuplicateKey(
|
||||
hasDuplicateKeys(newValue)
|
||||
);
|
||||
if (hasDuplicateKeys(newValue)) {
|
||||
setDictArr(newValue);
|
||||
} else {
|
||||
setDictArr(newValue);
|
||||
myData.node!.template[
|
||||
templateParam
|
||||
].value = newValue;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam]
|
||||
.multiline ? (
|
||||
) : myData.current.node.template[
|
||||
templateParam
|
||||
].multiline ? (
|
||||
<TextAreaComponent
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
|
|
@ -259,12 +236,14 @@ const EditNodeModal = forwardRef(
|
|||
editNode={true}
|
||||
disabled={disabled}
|
||||
password={
|
||||
myData.node.template[templateParam]
|
||||
.password ?? false
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].password ?? false
|
||||
}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
|
|
@ -272,14 +251,88 @@ const EditNodeModal = forwardRef(
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"bool" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "NestedDict" ? (
|
||||
<div className=" w-full">
|
||||
<DictComponent
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.current.node!.template[
|
||||
templateParam
|
||||
].value.toString() === "{}"
|
||||
? {
|
||||
yourkey: "value",
|
||||
}
|
||||
: myData.current.node!.template[
|
||||
templateParam
|
||||
].value
|
||||
}
|
||||
onChange={(newValue) => {
|
||||
myData.current.node!.template[
|
||||
templateParam
|
||||
].value = newValue;
|
||||
handleOnNewValue(newValue, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "dict" ? (
|
||||
<div
|
||||
className={classNames(
|
||||
"max-h-48 w-full overflow-auto custom-scroll",
|
||||
myData.current.node!.template[templateParam]
|
||||
.value?.length > 1
|
||||
? "my-3"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<KeypairListComponent
|
||||
dataValue={dataValue}
|
||||
advanced={adv}
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.current.node!.template[
|
||||
templateParam
|
||||
].value?.length === 0 ||
|
||||
!myData.current.node!.template[
|
||||
templateParam
|
||||
].value
|
||||
? [{ "": "" }]
|
||||
: convertObjToArray(
|
||||
myData.current.node!.template[
|
||||
templateParam
|
||||
].value
|
||||
)
|
||||
}
|
||||
duplicateKey={errorDuplicateKey}
|
||||
onChange={(newValue) => {
|
||||
const valueToNumbers =
|
||||
convertValuesToNumbers(newValue);
|
||||
myData.current.node!.template[
|
||||
templateParam
|
||||
].value = valueToNumbers;
|
||||
setErrorDuplicateKey(
|
||||
hasDuplicateKeys(valueToNumbers)
|
||||
);
|
||||
handleOnNewValue(
|
||||
valueToNumbers,
|
||||
templateParam
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "bool" ? (
|
||||
<div className="ml-auto">
|
||||
{" "}
|
||||
<ToggleShadComponent
|
||||
disabled={disabled}
|
||||
enabled={
|
||||
myData.node.template[templateParam].value
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value
|
||||
}
|
||||
setEnabled={(isEnabled) => {
|
||||
handleOnNewValue(
|
||||
|
|
@ -290,76 +343,84 @@ const EditNodeModal = forwardRef(
|
|||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"float" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "float" ? (
|
||||
<div className="mx-auto">
|
||||
<FloatComponent
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"str" &&
|
||||
myData.node.template[templateParam].options ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "str" &&
|
||||
myData.current.node.template[templateParam]
|
||||
.options ? (
|
||||
<div className="mx-auto">
|
||||
<Dropdown
|
||||
numberOfOptions={nodeLength}
|
||||
editNode={true}
|
||||
options={
|
||||
myData.node.template[templateParam]
|
||||
.options
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].options
|
||||
}
|
||||
onSelect={(value) =>
|
||||
handleOnNewValue(value, templateParam)
|
||||
}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? "Choose an option"
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? "Choose an option"
|
||||
}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"int" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "int" ? (
|
||||
<div className="mx-auto">
|
||||
<IntComponent
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"file" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "file" ? (
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
editNode={true}
|
||||
disabled={disabled}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
fileTypes={
|
||||
myData.node.template[templateParam]
|
||||
.fileTypes
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].fileTypes
|
||||
}
|
||||
suffixes={
|
||||
myData.node.template[templateParam]
|
||||
.suffixes
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].suffixes
|
||||
}
|
||||
onFileChange={(filePath: string) => {
|
||||
data.node!.template[
|
||||
|
|
@ -368,30 +429,47 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"prompt" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "prompt" ? (
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
readonly={
|
||||
myData.current.node?.flow &&
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
field_name={templateParam}
|
||||
editNode={true}
|
||||
disabled={disabled}
|
||||
nodeClass={myData.node}
|
||||
nodeClass={myData.current.node}
|
||||
setNodeClass={(nodeClass) => {
|
||||
myData.node = nodeClass;
|
||||
myData.current.node = nodeClass;
|
||||
}}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"code" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "code" ? (
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
readonly={
|
||||
myData.current.node?.flow &&
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
dynamic={
|
||||
data.node!.template[templateParam]
|
||||
.dynamic ?? false
|
||||
|
|
@ -403,16 +481,17 @@ const EditNodeModal = forwardRef(
|
|||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
myData.current.node.template[
|
||||
templateParam
|
||||
].value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"Any" ? (
|
||||
) : myData.current.node?.template[templateParam]
|
||||
.type === "Any" ? (
|
||||
"-"
|
||||
) : (
|
||||
<div className="hidden"></div>
|
||||
|
|
@ -421,13 +500,19 @@ const EditNodeModal = forwardRef(
|
|||
<TableCell className="p-0 text-right">
|
||||
<div className="items-center text-center">
|
||||
<ToggleShadComponent
|
||||
id={
|
||||
"show" +
|
||||
myData.current.node?.template[templateParam]
|
||||
.name
|
||||
}
|
||||
enabled={
|
||||
!myData.node?.template[templateParam]
|
||||
.advanced
|
||||
}
|
||||
setEnabled={(e) =>
|
||||
changeAdvanced(templateParam)
|
||||
!myData.current.node?.template[
|
||||
templateParam
|
||||
].advanced
|
||||
}
|
||||
setEnabled={(e) => {
|
||||
changeAdvanced(templateParam);
|
||||
}}
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
/>
|
||||
|
|
@ -445,9 +530,11 @@ const EditNodeModal = forwardRef(
|
|||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
id={"saveChangesBtn"}
|
||||
className="mt-3"
|
||||
onClick={() => {
|
||||
setData(cloneDeep(myData)); //saves data with actual state of modal
|
||||
const newData = cloneDeep(myData.current);
|
||||
myData.current = newData;
|
||||
//@ts-ignore
|
||||
setTabsState((prev: TabsState) => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ interface BaseModalProps {
|
|||
| "medium-h-full";
|
||||
|
||||
disable?: boolean;
|
||||
onChangeOpenModal?: (open: boolean) => void;
|
||||
onChangeOpenModal?: (open?: boolean) => void;
|
||||
}
|
||||
function BaseModal({
|
||||
open,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default function CodeAreaModal({
|
|||
setNodeClass,
|
||||
children,
|
||||
dynamic,
|
||||
readonly = false,
|
||||
}: codeAreaModalPropsType): JSX.Element {
|
||||
const [code, setCode] = useState(value);
|
||||
const { dark } = useContext(darkContext);
|
||||
|
|
@ -146,6 +147,7 @@ export default function CodeAreaModal({
|
|||
<div className="flex h-full w-full flex-col transition-all">
|
||||
<div className="h-full w-full">
|
||||
<AceEditor
|
||||
readOnly={readonly}
|
||||
value={code}
|
||||
mode="python"
|
||||
height={height ?? "100%"}
|
||||
|
|
@ -180,7 +182,12 @@ export default function CodeAreaModal({
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex h-fit w-full justify-end">
|
||||
<Button className="mt-3" onClick={handleClick} type="submit">
|
||||
<Button
|
||||
disabled={readonly}
|
||||
className="mt-3"
|
||||
onClick={handleClick}
|
||||
type="submit"
|
||||
>
|
||||
Check & Save
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "ace-builds/src-noconflict/mode-python";
|
|||
import "ace-builds/src-noconflict/theme-github";
|
||||
import "ace-builds/src-noconflict/theme-twilight";
|
||||
// import "ace-builds/webpack-resolver";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import JsonView from "react18-json-view";
|
||||
import "react18-json-view/src/dark.css";
|
||||
import "react18-json-view/src/style.css";
|
||||
|
|
@ -19,11 +19,12 @@ export default function DictAreaModal({
|
|||
value,
|
||||
}): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [dictObj, setDictObj] = useState(value);
|
||||
|
||||
const ref = useRef(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (value) setDictObj(value);
|
||||
}, [value]);
|
||||
if (value) ref.current = value;
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
||||
|
|
@ -37,23 +38,26 @@ export default function DictAreaModal({
|
|||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<div className="flex h-full w-full flex-col transition-all">
|
||||
<div className="flex h-full w-full flex-col transition-all ">
|
||||
<JsonView
|
||||
theme="vscode"
|
||||
dark={true}
|
||||
editable
|
||||
enableClipboard
|
||||
onEdit={(edit) => {
|
||||
setDictObj(edit["src"]);
|
||||
ref.current = edit["src"];
|
||||
}}
|
||||
src={dictObj}
|
||||
onChange={(edit) => {
|
||||
ref.current = edit["src"];
|
||||
}}
|
||||
src={ref.current}
|
||||
/>
|
||||
<div className="flex h-fit w-full justify-end">
|
||||
<Button
|
||||
className="mt-3"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onChange(dictObj);
|
||||
onChange(ref.current);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export default function FormModal({
|
|||
});
|
||||
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const template = useRef(tabsState[flow.id].formKeysData.template);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { accessToken } = useContext(AuthContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
|
@ -125,12 +126,13 @@ export default function FormModal({
|
|||
function updateLastMessage({
|
||||
str,
|
||||
thought,
|
||||
prompt,
|
||||
end = false,
|
||||
files,
|
||||
}: {
|
||||
str?: string;
|
||||
thought?: string;
|
||||
// end param default is false
|
||||
prompt?: string;
|
||||
end?: boolean;
|
||||
files?: Array<any>;
|
||||
}) {
|
||||
|
|
@ -150,12 +152,22 @@ export default function FormModal({
|
|||
if (files) {
|
||||
newChat[newChat.length - 1].files = files;
|
||||
}
|
||||
if (prompt) {
|
||||
newChat[newChat.length - 2].template = prompt;
|
||||
}
|
||||
return newChat;
|
||||
});
|
||||
}
|
||||
|
||||
function handleOnClose(event: CloseEvent): void {
|
||||
if (isOpen.current) {
|
||||
//check if the user has been logged out, if so close the chat when the user is redirected to the login page
|
||||
if (window.location.href.includes("login")) {
|
||||
setOpen(false);
|
||||
ws.current?.close();
|
||||
return;
|
||||
}
|
||||
|
||||
getBuildStatus(flow.id)
|
||||
.then((response) => {
|
||||
if (response.data.built) {
|
||||
|
|
@ -194,17 +206,26 @@ export default function FormModal({
|
|||
}
|
||||
|
||||
function handleWsMessage(data: any) {
|
||||
console.log(data);
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
//set chat history
|
||||
setChatHistory((_) => {
|
||||
console.log(data);
|
||||
let newChatHistory: ChatMessageType[] = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].type === "prompt" && data[i].prompt) {
|
||||
if (data[i - 1] && !data[i - 1].is_bot) {
|
||||
data[i - 1].prompt = data[i].prompt;
|
||||
template.current = data[i].prompt;
|
||||
}
|
||||
}
|
||||
}
|
||||
data = data.filter((item: any) => item.type !== "prompt");
|
||||
data.forEach(
|
||||
(chatItem: {
|
||||
intermediate_steps?: string;
|
||||
is_bot: boolean;
|
||||
message: string;
|
||||
template: string;
|
||||
prompt?: string;
|
||||
type: string;
|
||||
chatKey: string;
|
||||
files?: Array<any>;
|
||||
|
|
@ -215,7 +236,7 @@ export default function FormModal({
|
|||
? {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.message,
|
||||
template: chatItem.template,
|
||||
template: chatItem.prompt,
|
||||
thought: chatItem.intermediate_steps,
|
||||
files: chatItem.files,
|
||||
chatKey: chatItem.chatKey,
|
||||
|
|
@ -223,7 +244,7 @@ export default function FormModal({
|
|||
: {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.message,
|
||||
template: chatItem.template,
|
||||
template: chatItem.prompt,
|
||||
thought: chatItem.intermediate_steps,
|
||||
chatKey: chatItem.chatKey,
|
||||
}
|
||||
|
|
@ -240,7 +261,11 @@ export default function FormModal({
|
|||
}
|
||||
if (data.type === "end") {
|
||||
if (data.message) {
|
||||
updateLastMessage({ str: data.message, end: true });
|
||||
updateLastMessage({
|
||||
str: data.message,
|
||||
end: true,
|
||||
prompt: template.current,
|
||||
});
|
||||
}
|
||||
if (data.intermediate_steps) {
|
||||
updateLastMessage({
|
||||
|
|
@ -255,10 +280,12 @@ export default function FormModal({
|
|||
files: data.files,
|
||||
});
|
||||
}
|
||||
|
||||
setLockChat(false);
|
||||
isStream = false;
|
||||
}
|
||||
if (data.type == "prompt" && data.prompt) {
|
||||
template.current = data.prompt;
|
||||
}
|
||||
if (data.type === "stream" && isStream) {
|
||||
updateLastMessage({ str: data.message });
|
||||
}
|
||||
|
|
@ -283,18 +310,8 @@ export default function FormModal({
|
|||
handleOnClose(event);
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
if (flow.id === "") {
|
||||
connectWS();
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "There was an error on web connection, please: ",
|
||||
list: [
|
||||
"Refresh the page",
|
||||
"Use a new flow tab",
|
||||
"Check if the backend is up",
|
||||
],
|
||||
});
|
||||
}
|
||||
console.log(ev);
|
||||
connectWS();
|
||||
};
|
||||
ws.current = newWs;
|
||||
} catch (error) {
|
||||
|
|
@ -316,6 +333,15 @@ export default function FormModal({
|
|||
// do not add connectWS on dependencies array
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (ws.current) {
|
||||
console.log("closing ws");
|
||||
ws.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
ws.current &&
|
||||
|
|
@ -356,24 +382,23 @@ export default function FormModal({
|
|||
}, [open]);
|
||||
|
||||
function sendMessage(): void {
|
||||
let nodeValidationErrors = validateNodes(reactFlowInstance!);
|
||||
let nodeValidationErrors = validateNodes(
|
||||
reactFlowInstance!.getNodes(),
|
||||
reactFlowInstance!.getEdges()
|
||||
);
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
setLockChat(true);
|
||||
let inputs = tabsState[id.current].formKeysData.input_keys;
|
||||
setChatValue("");
|
||||
const message = inputs;
|
||||
addChatHistory(
|
||||
message!,
|
||||
true,
|
||||
chatKey!,
|
||||
tabsState[flow.id].formKeysData.template
|
||||
);
|
||||
addChatHistory(message!, true, chatKey!, template.current);
|
||||
sendAll({
|
||||
...reactFlowInstance?.toObject()!,
|
||||
inputs: inputs!,
|
||||
chatHistory,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
chatKey: chatKey!,
|
||||
});
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
|
|
@ -391,6 +416,7 @@ export default function FormModal({
|
|||
}
|
||||
function clearChat(): void {
|
||||
setChatHistory([]);
|
||||
template.current = tabsState[id.current].formKeysData.template;
|
||||
ws.current?.send(JSON.stringify({ clear_history: true }));
|
||||
if (lockChat) setLockChat(false);
|
||||
}
|
||||
|
|
@ -404,7 +430,6 @@ export default function FormModal({
|
|||
setChatValue("");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger hidden></DialogTrigger>
|
||||
|
|
|
|||
|
|
@ -17,11 +17,7 @@ import { alertContext } from "../../contexts/alertContext";
|
|||
import { postValidatePrompt } from "../../controllers/API";
|
||||
import { genericModalPropsType } from "../../types/components";
|
||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
classNames,
|
||||
getRandomKeyByssmm,
|
||||
varHighlightHTML,
|
||||
} from "../../utils/utils";
|
||||
import { classNames, varHighlightHTML } from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function GenericModal({
|
||||
|
|
@ -34,9 +30,11 @@ export default function GenericModal({
|
|||
nodeClass,
|
||||
setNodeClass,
|
||||
children,
|
||||
readonly = false,
|
||||
}: genericModalPropsType): JSX.Element {
|
||||
const [myButtonText] = useState(buttonText);
|
||||
const [myModalTitle] = useState(modalTitle);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [myModalType] = useState(type);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isEdit, setIsEdit] = useState(true);
|
||||
|
|
@ -89,7 +87,7 @@ export default function GenericModal({
|
|||
|
||||
useEffect(() => {
|
||||
setInputValue(value);
|
||||
}, [value]);
|
||||
}, [value, modalOpen]);
|
||||
|
||||
const coloredContent = (inputValue || "")
|
||||
.replace(/</g, "<")
|
||||
|
|
@ -97,19 +95,6 @@ export default function GenericModal({
|
|||
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
|
||||
.replace(/\n/g, "<br />");
|
||||
|
||||
const TextAreaContentView = (): JSX.Element => {
|
||||
return (
|
||||
<SanitizedHTMLWrapper
|
||||
className={getClassByNumberLength()}
|
||||
content={coloredContent}
|
||||
onClick={() => {
|
||||
setIsEdit(true);
|
||||
}}
|
||||
suppressWarning={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getClassByNumberLength(): string {
|
||||
let sumOfCaracteres: number = 0;
|
||||
wordsHighlight.forEach((element) => {
|
||||
|
|
@ -125,12 +110,17 @@ export default function GenericModal({
|
|||
|
||||
postValidatePrompt(field_name, inputValue, nodeClass!)
|
||||
.then((apiReturn) => {
|
||||
// if field_name is an empty string, then we need to set it
|
||||
// to the first key of the custom_fields object
|
||||
if (field_name === "") {
|
||||
console.log(apiReturn.data?.frontend_node?.custom_fields);
|
||||
field_name = Array.isArray(
|
||||
apiReturn.data?.frontend_node?.custom_fields?.[""]
|
||||
)
|
||||
? apiReturn.data?.frontend_node?.custom_fields?.[""][0] ?? ""
|
||||
: apiReturn.data?.frontend_node?.custom_fields?.[""] ?? "";
|
||||
}
|
||||
if (apiReturn.data) {
|
||||
setValue(inputValue);
|
||||
apiReturn.data.frontend_node["template"]["template"]["value"] =
|
||||
inputValue;
|
||||
setNodeClass!(apiReturn?.data?.frontend_node);
|
||||
|
||||
let inputVariables = apiReturn.data.input_variables ?? [];
|
||||
if (inputVariables && inputVariables.length === 0) {
|
||||
setIsEdit(true);
|
||||
|
|
@ -138,6 +128,17 @@ export default function GenericModal({
|
|||
title: "Your template does not have any variables.",
|
||||
});
|
||||
setModalOpen(false);
|
||||
if (
|
||||
JSON.stringify(apiReturn.data?.frontend_node) !==
|
||||
JSON.stringify({})
|
||||
)
|
||||
setNodeClass!(apiReturn.data?.frontend_node);
|
||||
setModalOpen(closeModal);
|
||||
setValue(inputValue);
|
||||
if (field_name !== "") {
|
||||
apiReturn.data.frontend_node["template"][field_name]["value"] =
|
||||
inputValue;
|
||||
}
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
setSuccessData({
|
||||
|
|
@ -150,6 +151,10 @@ export default function GenericModal({
|
|||
setNodeClass!(apiReturn.data?.frontend_node);
|
||||
setModalOpen(closeModal);
|
||||
setValue(inputValue);
|
||||
if (field_name !== "") {
|
||||
apiReturn.data.frontend_node["template"][field_name]["value"] =
|
||||
inputValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setIsEdit(true);
|
||||
|
|
@ -159,16 +164,15 @@ export default function GenericModal({
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setIsEdit(true);
|
||||
return setErrorData({
|
||||
title: "There is something wrong with this prompt, please review it",
|
||||
list: [error?.response?.data?.detail],
|
||||
list: [error.toString()],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
onChangeOpenModal={(open) => {}}
|
||||
|
|
@ -205,7 +209,7 @@ export default function GenericModal({
|
|||
"flex h-full w-full"
|
||||
)}
|
||||
>
|
||||
{type === TypeModal.PROMPT && isEdit ? (
|
||||
{type === TypeModal.PROMPT && isEdit && !readonly ? (
|
||||
<Textarea
|
||||
ref={divRefPrompt}
|
||||
className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
|
||||
|
|
@ -223,8 +227,15 @@ export default function GenericModal({
|
|||
handleKeyDown(e, inputValue, "");
|
||||
}}
|
||||
/>
|
||||
) : type === TypeModal.PROMPT && !isEdit ? (
|
||||
<TextAreaContentView />
|
||||
) : type === TypeModal.PROMPT && (!isEdit || readonly) ? (
|
||||
<SanitizedHTMLWrapper
|
||||
className={getClassByNumberLength()}
|
||||
content={coloredContent}
|
||||
onClick={() => {
|
||||
setIsEdit(true);
|
||||
}}
|
||||
suppressWarning={true}
|
||||
/>
|
||||
) : type !== TypeModal.PROMPT ? (
|
||||
<Textarea
|
||||
//@ts-ignore
|
||||
|
|
@ -238,6 +249,7 @@ export default function GenericModal({
|
|||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
}}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
|
@ -263,7 +275,7 @@ export default function GenericModal({
|
|||
|
||||
{wordsHighlight.map((word, index) => (
|
||||
<ShadTooltip
|
||||
key={getRandomKeyByssmm() + index}
|
||||
key={index}
|
||||
content={word.replace(/[{}]/g, "")}
|
||||
asChild={false}
|
||||
>
|
||||
|
|
@ -294,6 +306,7 @@ export default function GenericModal({
|
|||
)}
|
||||
</div>
|
||||
<Button
|
||||
disabled={readonly}
|
||||
onClick={() => {
|
||||
switch (myModalType) {
|
||||
case TypeModal.TEXT:
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
import { buttonBoxPropsType } from "../../../types/components";
|
||||
import { classNames } from "../../../utils/utils";
|
||||
|
||||
export default function ButtonBox({
|
||||
onClick,
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
bgColor,
|
||||
textColor,
|
||||
deactivate,
|
||||
size,
|
||||
}: buttonBoxPropsType): JSX.Element {
|
||||
let bigCircle: string;
|
||||
let smallCircle: string;
|
||||
let titleFontSize: string;
|
||||
let descriptionFontSize: string;
|
||||
let padding: string;
|
||||
let marginTop: string;
|
||||
let height: string;
|
||||
let width: string;
|
||||
let textHeight: number;
|
||||
let textWidth: number;
|
||||
switch (size) {
|
||||
case "small":
|
||||
bigCircle = "h-12 w-12";
|
||||
smallCircle = "h-8 w-8";
|
||||
titleFontSize = "text-sm";
|
||||
descriptionFontSize = "text-xs";
|
||||
padding = "p-2 py-3";
|
||||
marginTop = "mt-2";
|
||||
height = "h-36";
|
||||
width = "w-32";
|
||||
break;
|
||||
case "medium":
|
||||
bigCircle = "h-16 w-16";
|
||||
smallCircle = "h-12 w-12";
|
||||
titleFontSize = "text-base";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-4 py-5";
|
||||
marginTop = "mt-3";
|
||||
height = "h-44";
|
||||
width = "w-36";
|
||||
break;
|
||||
case "big":
|
||||
bigCircle = "h-20 w-20";
|
||||
smallCircle = "h-16 w-16";
|
||||
titleFontSize = "text-lg";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-8 py-10";
|
||||
marginTop = "mt-6";
|
||||
height = "h-56";
|
||||
width = "w-44";
|
||||
break;
|
||||
default:
|
||||
bigCircle = "h-20 w-20";
|
||||
smallCircle = "h-16 w-16";
|
||||
titleFontSize = "text-lg";
|
||||
descriptionFontSize = "text-sm";
|
||||
padding = "p-8 py-10";
|
||||
marginTop = "mt-6";
|
||||
height = "h-56";
|
||||
width = "w-44";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<button disabled={deactivate} onClick={onClick}>
|
||||
<div
|
||||
className={classNames(
|
||||
"button-box-modal-div",
|
||||
bgColor,
|
||||
height,
|
||||
width,
|
||||
padding
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center ${bigCircle} mb-1 rounded-full bg-background/30`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center ${smallCircle} rounded-full bg-background`}
|
||||
>
|
||||
<div className={textColor}>{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-auto mt-auto w-full">
|
||||
<h3
|
||||
className={classNames(
|
||||
"w-full font-semibold text-background truncate-multiline word-break-break-word",
|
||||
titleFontSize,
|
||||
marginTop
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ComputerDesktopIcon,
|
||||
DocumentDuplicateIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import LoadingComponent from "../../components/loadingComponent";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { IMPORT_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { getExamples } from "../../controllers/API";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import ButtonBox from "./buttonBox";
|
||||
|
||||
export default function ImportModal(): JSX.Element {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const ref = useRef();
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [loadingExamples, setLoadingExamples] = useState(false);
|
||||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||
|
||||
function handleExamples(): void {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
setLoadingExamples(false);
|
||||
setExamples(result);
|
||||
})
|
||||
.catch((error) =>
|
||||
setErrorData({
|
||||
title: "there was an error loading examples, please try again",
|
||||
list: [error.message],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogTrigger></DialogTrigger>
|
||||
<DialogContent
|
||||
className={classNames(
|
||||
showExamples
|
||||
? "h-[600px] lg:max-w-[650px]"
|
||||
: "h-[450px] lg:max-w-[650px]"
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">
|
||||
{showExamples && (
|
||||
<>
|
||||
<div className="dialog-header-modal-div">
|
||||
<button
|
||||
type="button"
|
||||
className="dialog-header-modal-button disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
onClick={() => {
|
||||
setShowExamples(false);
|
||||
}}
|
||||
>
|
||||
<ArrowLeftIcon
|
||||
className="ml-1 h-5 w-5 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<span className={classNames(showExamples ? "pl-8 pr-2" : "pr-2")}>
|
||||
{showExamples ? "Select an example" : "Import"}
|
||||
</span>
|
||||
<ArrowUpTrayIcon
|
||||
className="ml-1 h-5 w-5 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogDescription>{IMPORT_DIALOG_SUBTITLE}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
"dialog-modal-examples-div",
|
||||
showExamples && !loadingExamples
|
||||
? "dialog-modal-example-true"
|
||||
: "dialog-modal-example-false"
|
||||
)}
|
||||
>
|
||||
{!showExamples && (
|
||||
<div className="dialog-modal-button-box-div">
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-medium-emerald "
|
||||
description="Prebuilt Examples"
|
||||
icon={<DocumentDuplicateIcon className="document-icon" />}
|
||||
onClick={() => {
|
||||
setShowExamples(true);
|
||||
handleExamples();
|
||||
}}
|
||||
textColor="text-medium-emerald "
|
||||
title="Examples"
|
||||
></ButtonBox>
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-almost-dark-blue "
|
||||
description="Import from Local"
|
||||
icon={<ComputerDesktopIcon className="document-icon" />}
|
||||
onClick={() => {
|
||||
uploadFlow();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-almost-dark-blue "
|
||||
title="Local File"
|
||||
></ButtonBox>
|
||||
</div>
|
||||
)}
|
||||
{showExamples && loadingExamples && (
|
||||
<div className="loading-component-div">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
)}
|
||||
{showExamples &&
|
||||
!loadingExamples &&
|
||||
examples.map((example, index) => {
|
||||
return (
|
||||
<div key={example.name} className="m-2">
|
||||
{" "}
|
||||
<ButtonBox
|
||||
size="small"
|
||||
bgColor="bg-medium-emerald "
|
||||
description={example.description ?? "Prebuilt Examples"}
|
||||
icon={
|
||||
<DocumentDuplicateIcon
|
||||
strokeWidth={1.5}
|
||||
className="h-6 w-6 flex-shrink-0"
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
addFlow(example, false);
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-medium-emerald "
|
||||
title={example.name}
|
||||
></ButtonBox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<div className="dialog-modal-footer">
|
||||
<a
|
||||
href="https://github.com/logspace-ai/langflow_examples"
|
||||
target="_blank"
|
||||
className="dialog-modal-footer-link "
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
viewBox="0 0 98 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className="ml-2 ">Langflow Examples</span>
|
||||
</a>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -54,7 +54,9 @@ export default function LoginAdminPage() {
|
|||
.then((user) => {
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
.catch((error) => {
|
||||
console.log("login admin page", error);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default function AdminPage() {
|
|||
|
||||
function getUsers() {
|
||||
setLoadingUsers(true);
|
||||
getUsersPage(index, size)
|
||||
getUsersPage(index - 1, size)
|
||||
.then((users) => {
|
||||
setTotalRowsCount(users["total_count"]);
|
||||
userList.current = users["users"];
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export default function CommunityPage(): JSX.Element {
|
|||
size="sm"
|
||||
className="whitespace-nowrap "
|
||||
onClick={() => {
|
||||
addFlow(flow, true).then((id) => {
|
||||
addFlow(true, flow).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default function DisclosureComponent({
|
|||
<div>
|
||||
<Disclosure.Button className="components-disclosure-arrangement">
|
||||
<div className="flex gap-4">
|
||||
{/* BUG ON THIS ICON */}
|
||||
<Icon strokeWidth={1.5} size={22} className="text-primary" />
|
||||
<span className="components-disclosure-title">{title}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,11 +33,18 @@ import { TabsContext } from "../../../../contexts/tabsContext";
|
|||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
||||
import { APIClassType } from "../../../../types/api";
|
||||
import { FlowType, NodeType } from "../../../../types/flow";
|
||||
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
|
||||
import { TabsState } from "../../../../types/tabs";
|
||||
import { isValidConnection } from "../../../../utils/reactflowUtils";
|
||||
import { isWrappedWithClass } from "../../../../utils/utils";
|
||||
import {
|
||||
generateFlow,
|
||||
generateNodeFromFlow,
|
||||
isValidConnection,
|
||||
scapeJSONParse,
|
||||
validateSelection,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||
import SelectionMenu from "../SelectionMenuComponent";
|
||||
import ExtraSidebar from "../extraSidebarComponent";
|
||||
|
||||
const nodeTypes = {
|
||||
|
|
@ -70,6 +77,8 @@ export default function Page({
|
|||
setReactFlowInstance,
|
||||
templates,
|
||||
setFilterEdge,
|
||||
deleteNode,
|
||||
deleteEdge,
|
||||
} = useContext(typesContext);
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -112,6 +121,16 @@ export default function Page({
|
|||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
if (!isWrappedWithClass(event, "nodelete")) {
|
||||
if (
|
||||
(event.key === "Delete" || event.key === "Backspace") &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleMouseMove = (event) => {
|
||||
setPosition({ x: event.clientX, y: event.clientY });
|
||||
|
|
@ -160,6 +179,27 @@ export default function Page({
|
|||
setExtraNavigation({ title: "Components" });
|
||||
}, [setExtraComponent, setExtraNavigation]);
|
||||
|
||||
const [seconds, setSeconds] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setSeconds((prevSeconds) => {
|
||||
let updatedSeconds = prevSeconds + 1;
|
||||
|
||||
if (updatedSeconds % 30 === 0) {
|
||||
saveFlow(flow, true);
|
||||
updatedSeconds = 0;
|
||||
}
|
||||
|
||||
return updatedSeconds;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onEdgesChangeMod = useCallback(
|
||||
(change: EdgeChange[]) => {
|
||||
onEdgesChange(change);
|
||||
|
|
@ -205,12 +245,19 @@ export default function Page({
|
|||
addEdge(
|
||||
{
|
||||
...params,
|
||||
data: {
|
||||
targetHandle: scapeJSONParse(params.targetHandle!),
|
||||
sourceHandle: scapeJSONParse(params.sourceHandle!),
|
||||
},
|
||||
style: { stroke: "#555" },
|
||||
className:
|
||||
(params.targetHandle?.split("|")[0] === "Text"
|
||||
((scapeJSONParse(params.targetHandle!) as targetHandleType)
|
||||
.type === "Text"
|
||||
? "stroke-foreground "
|
||||
: "stroke-foreground ") + " stroke-connection",
|
||||
animated: params.targetHandle?.split("|")[0] === "Text",
|
||||
animated:
|
||||
(scapeJSONParse(params.targetHandle!) as targetHandleType)
|
||||
.type === "Text",
|
||||
},
|
||||
eds
|
||||
)
|
||||
|
|
@ -304,7 +351,7 @@ export default function Page({
|
|||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||
takeSnapshot();
|
||||
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
||||
uploadFlow(true, event.dataTransfer.files.item(0)!);
|
||||
uploadFlow(false, event.dataTransfer.files.item(0)!);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Invalid file type",
|
||||
|
|
@ -361,7 +408,7 @@ export default function Page({
|
|||
edgeUpdateSuccessful.current = true;
|
||||
}, []);
|
||||
|
||||
const [selectionEnded, setSelectionEnded] = useState(false);
|
||||
const [selectionEnded, setSelectionEnded] = useState(true);
|
||||
|
||||
const onSelectionEnd = useCallback(() => {
|
||||
setSelectionEnded(true);
|
||||
|
|
@ -401,7 +448,7 @@ export default function Page({
|
|||
<div className="h-full w-full" ref={reactFlowWrapper}>
|
||||
{Object.keys(templates).length > 0 &&
|
||||
Object.keys(types).length > 0 ? (
|
||||
<div className="h-full w-full">
|
||||
<div id="react-flow-id" className="h-full w-full">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onMove={() => {
|
||||
|
|
@ -429,8 +476,9 @@ export default function Page({
|
|||
connectionLineComponent={ConnectionLineComponent}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
onNodesDelete={onDelete}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onNodesDelete={onDelete}
|
||||
deleteKeyCode={[]}
|
||||
className="theme-attribution"
|
||||
minZoom={0.01}
|
||||
maxZoom={8}
|
||||
|
|
@ -447,6 +495,50 @@ export default function Page({
|
|||
[&>button]:border-b-border hover:[&>button]:bg-border"
|
||||
></Controls>
|
||||
)}
|
||||
<SelectionMenu
|
||||
isVisible={selectionMenuVisible}
|
||||
nodes={lastSelection?.nodes}
|
||||
onClick={() => {
|
||||
if (
|
||||
validateSelection(lastSelection!, edges).length === 0
|
||||
) {
|
||||
const { newFlow } = generateFlow(
|
||||
lastSelection!,
|
||||
reactFlowInstance!,
|
||||
getRandomName()
|
||||
);
|
||||
const newGroupNode = generateNodeFromFlow(
|
||||
newFlow,
|
||||
getNodeId
|
||||
);
|
||||
setNodes((oldNodes) => [
|
||||
...oldNodes.filter(
|
||||
(oldNodes) =>
|
||||
!lastSelection?.nodes.some(
|
||||
(selectionNode) =>
|
||||
selectionNode.id === oldNodes.id
|
||||
)
|
||||
),
|
||||
newGroupNode,
|
||||
]);
|
||||
setEdges((oldEdges) =>
|
||||
oldEdges.filter(
|
||||
(oldEdge) =>
|
||||
!lastSelection!.nodes.some(
|
||||
(selectionNode) =>
|
||||
selectionNode.id === oldEdge.target ||
|
||||
selectionNode.id === oldEdge.source
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Invalid selection",
|
||||
list: validateSelection(lastSelection!, edges),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ReactFlow>
|
||||
{!view && (
|
||||
<Chat flow={flow} reactFlowInstance={reactFlowInstance!} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { NodeToolbar } from "reactflow";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
export default function SelectionMenu({ onClick, nodes, isVisible }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [lastNodes, setLastNodes] = useState(nodes);
|
||||
|
||||
// nodes get saved to not be gone after the toolbar closes
|
||||
useEffect(() => {
|
||||
setLastNodes(nodes);
|
||||
}, [isOpen]);
|
||||
|
||||
// transition starts after and ends before the toolbar closes
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
setIsOpen(true);
|
||||
setTimeout(() => {
|
||||
setIsTransitioning(true);
|
||||
}, 50);
|
||||
} else {
|
||||
setIsTransitioning(false);
|
||||
setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
}, 500);
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<NodeToolbar
|
||||
isVisible={isOpen}
|
||||
offset={5}
|
||||
nodeId={
|
||||
lastNodes && lastNodes.length > 0 ? lastNodes.map((n) => n.id) : []
|
||||
}
|
||||
>
|
||||
<div className="h-10 w-28 overflow-hidden">
|
||||
<div
|
||||
className={
|
||||
"h-10 w-24 rounded-md border border-indigo-300 bg-white px-2.5 text-gray-700 shadow-inner transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300" +
|
||||
(isTransitioning ? " translate-y-0" : " translate-y-10")
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="flex h-full w-full items-center justify-between text-sm hover:text-indigo-500"
|
||||
onClick={onClick}
|
||||
>
|
||||
<IconComponent name="Group" className="w-6" />
|
||||
Group
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</NodeToolbar>
|
||||
);
|
||||
}
|
||||
|
|
@ -81,9 +81,11 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
}, []);
|
||||
|
||||
function handleBlur() {
|
||||
setFilterData(data);
|
||||
setFilterEdge([]);
|
||||
setSearch("");
|
||||
if (!search && search === "") {
|
||||
setFilterData(data);
|
||||
setFilterEdge([]);
|
||||
setSearch("");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -139,7 +141,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
<button
|
||||
className="extra-side-bar-buttons"
|
||||
onClick={() => {
|
||||
uploadFlow();
|
||||
uploadFlow(false);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="FileUp" className="side-bar-button-size " />
|
||||
|
|
@ -208,7 +210,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
name="search"
|
||||
id="search"
|
||||
placeholder="Search"
|
||||
className="nopan nodrag noundo nocopy input-search"
|
||||
className="nopan nodelete nodrag noundo nocopy input-search"
|
||||
onChange={(event) => {
|
||||
handleSearchInput(event.target.value);
|
||||
// Set search input state
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default function SidebarDraggableComponent({
|
|||
);
|
||||
}}
|
||||
>
|
||||
<div className="side-bar-components-div-form">
|
||||
<div id={display_name} className="side-bar-components-div-form">
|
||||
<span className="side-bar-components-text">{display_name}</span>
|
||||
<div>
|
||||
<SelectTrigger>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { nodeToolbarPropsType } from "../../../../types/components";
|
|||
import {
|
||||
createFlowComponent,
|
||||
downloadNode,
|
||||
expandGroupNode,
|
||||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
|
||||
|
|
@ -23,6 +25,7 @@ export default function NodeToolbarComponent({
|
|||
data,
|
||||
setData,
|
||||
deleteNode,
|
||||
position,
|
||||
setShowNode,
|
||||
numberOfHandles,
|
||||
showNode,
|
||||
|
|
@ -54,6 +57,8 @@ export default function NodeToolbarComponent({
|
|||
return true;
|
||||
}
|
||||
const isMinimal = canMinimize();
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
||||
const { paste } = useContext(TabsContext);
|
||||
const { saveComponent } = useContext(typesContext);
|
||||
const reactFlowInstance = useReactFlow();
|
||||
|
|
@ -81,6 +86,12 @@ export default function NodeToolbarComponent({
|
|||
saveComponent(data, autoLogin ? "auto" : userData?.id!);
|
||||
}
|
||||
break;
|
||||
case "disabled":
|
||||
break;
|
||||
case "ungroup":
|
||||
updateFlowPosition(position, data.node?.flow!);
|
||||
expandGroupNode(data, reactFlowInstance);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -172,7 +183,7 @@ export default function NodeToolbarComponent({
|
|||
</SelectTrigger>
|
||||
</ShadTooltip>
|
||||
<SelectContent>
|
||||
<SelectItem value={"advanced"}>
|
||||
<SelectItem value={nodeLength == 0 ? "disabled" : "advanced"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Settings2"
|
||||
|
|
@ -219,6 +230,17 @@ export default function NodeToolbarComponent({
|
|||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
{isGroup && (
|
||||
<SelectItem value="ungroup">
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Ungroup"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Ungroup{" "}
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
|
@ -235,29 +257,6 @@ export default function NodeToolbarComponent({
|
|||
<></>
|
||||
</EditNodeModal>
|
||||
)}
|
||||
|
||||
{/*
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<div>
|
||||
<EditNodeModal
|
||||
data={data}
|
||||
setData={setData}
|
||||
nodeLength={nodeLength}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
|
||||
(!canMinimize() && " rounded-r-md ") +
|
||||
(nodeLength == 0
|
||||
? " text-muted-foreground"
|
||||
: " text-foreground")
|
||||
)}
|
||||
>
|
||||
<IconComponent name="Settings2" className="h-4 w-4 " />
|
||||
</div>
|
||||
</EditNodeModal>
|
||||
</div>
|
||||
</ShadTooltip> */}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -41,12 +41,16 @@ export default function HomePage(): JSX.Element {
|
|||
|
||||
const dragOver = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
};
|
||||
|
||||
const dragEnter = (e) => {
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const dragLeave = () => {
|
||||
|
|
@ -100,7 +104,7 @@ export default function HomePage(): JSX.Element {
|
|||
<DropdownButton
|
||||
firstButtonName="New Project"
|
||||
onFirstBtnClick={() => {
|
||||
addFlow(null!, true).then((id) => {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@ export default function LoginPage(): JSX.Element {
|
|||
setIsAdmin(isSuperUser);
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
.catch((error) => {
|
||||
console.log("login page", error);
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
@apply flex flex-col gap-2 p-2;
|
||||
}
|
||||
.side-bar-components-div-arrangement {
|
||||
@apply w-full overflow-auto scrollbar-hide pb-10;
|
||||
@apply w-full overflow-auto pb-10 scrollbar-hide;
|
||||
}
|
||||
.search-icon {
|
||||
@apply absolute inset-y-0 right-0 flex items-center py-1.5 pr-5;
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
}
|
||||
|
||||
.skeleton-card {
|
||||
@apply bg-background h-48 p-4 border rounded-lg flex flex-col gap-6;
|
||||
@apply flex h-48 flex-col gap-6 rounded-lg border bg-background p-4;
|
||||
}
|
||||
|
||||
.skeleton-card-wrapper {
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
}
|
||||
|
||||
.loading-page-panel {
|
||||
@apply h-full w-full flex justify-center items-center bg-muted;
|
||||
@apply flex h-full w-full items-center justify-center bg-muted;
|
||||
}
|
||||
|
||||
.main-page-panel {
|
||||
|
|
@ -350,7 +350,7 @@
|
|||
@apply alert-icon text-status-green;
|
||||
}
|
||||
.success-alert-message {
|
||||
@apply word-break-break-word alert-font-size text-success-foreground;
|
||||
@apply alert-font-size text-success-foreground word-break-break-word;
|
||||
}
|
||||
|
||||
.card-component-title-display {
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
@apply flex h-7 w-7 items-center justify-center rounded-full text-2xl;
|
||||
}
|
||||
.card-component-title-size {
|
||||
@apply w-full flex-1 word-break-break-word truncate-doubleline;
|
||||
@apply w-full flex-1 truncate-doubleline word-break-break-word;
|
||||
}
|
||||
.card-component-delete-button {
|
||||
@apply flex self-start;
|
||||
|
|
@ -505,7 +505,7 @@
|
|||
}
|
||||
|
||||
.header-arrangement {
|
||||
@apply flex-max-width h-12 items-center justify-between border-border bg-muted;
|
||||
@apply flex-max-width h-12 items-center justify-between border-border bg-background;
|
||||
}
|
||||
.header-start-display {
|
||||
@apply flex w-[30%] items-center justify-start gap-2;
|
||||
|
|
@ -520,7 +520,7 @@
|
|||
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 shadow-sm;
|
||||
}
|
||||
.header-waitlist-link-box {
|
||||
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-2 shadow-sm text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50 whitespace-nowrap;
|
||||
@apply inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md border border-input px-2 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background disabled:pointer-events-none disabled:opacity-50;
|
||||
}
|
||||
.header-waitlist-link-box:hover {
|
||||
@apply hover:bg-accent hover:text-accent-foreground;
|
||||
|
|
@ -853,7 +853,7 @@
|
|||
}
|
||||
|
||||
.api-modal-tabs {
|
||||
@apply flex h-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] lg:w-full;
|
||||
@apply flex h-full max-w-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full;
|
||||
}
|
||||
.api-modal-tablist-div {
|
||||
@apply flex items-center justify-between px-2;
|
||||
|
|
@ -882,11 +882,11 @@
|
|||
@apply bg-white text-primary;
|
||||
}
|
||||
.code-highlight {
|
||||
@apply block max-h-[64vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
|
||||
@apply block max-h-[64vh] w-full overflow-y-hidden border-0 px-3 py-2 text-sm outline-0 word-break-break-word;
|
||||
}
|
||||
|
||||
.code-nohighlight {
|
||||
@apply block max-h-[70vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
|
||||
@apply block max-h-[70vh] w-full overflow-y-hidden border-0 px-3 py-2 text-sm outline-0 word-break-break-word;
|
||||
}
|
||||
.form-modal-lockchat {
|
||||
@apply form-input block w-full rounded-md border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-ring sm:text-sm;
|
||||
|
|
@ -1050,15 +1050,15 @@
|
|||
@apply rounded-md bg-indigo-100 px-0.5 dark:bg-indigo-900;
|
||||
}
|
||||
|
||||
.field-invalid{
|
||||
@apply font-medium text-[0.8rem] text-destructive absolute
|
||||
.field-invalid {
|
||||
@apply absolute text-[0.8rem] font-medium text-destructive;
|
||||
}
|
||||
|
||||
.label-invalid{
|
||||
@apply text-destructive
|
||||
.label-invalid {
|
||||
@apply text-destructive;
|
||||
}
|
||||
|
||||
.input-invalid{
|
||||
@apply border-destructive focus:ring-destructive focus:border-destructive
|
||||
.input-invalid {
|
||||
@apply border-destructive focus:border-destructive focus:ring-destructive;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
import { Edge, Node, Viewport } from "reactflow";
|
||||
import { FlowType } from "../flow";
|
||||
//kind and class are just representative names to represent the actual structure of the object received by the API
|
||||
export type APIDataType = { [key: string]: APIKindType };
|
||||
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
|
||||
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
|
||||
export type APITemplateType = {
|
||||
variable: TemplateVariableType;
|
||||
[key: string]: TemplateVariableType;
|
||||
};
|
||||
|
||||
export type CustomFieldsType = {
|
||||
[key: string]: Array<string>;
|
||||
};
|
||||
|
||||
export type APIClassType = {
|
||||
base_classes: Array<string>;
|
||||
description: string;
|
||||
|
|
@ -14,11 +19,21 @@ export type APIClassType = {
|
|||
display_name: string;
|
||||
input_types?: Array<string>;
|
||||
output_types?: Array<string>;
|
||||
custom_fields?: CustomFieldsType;
|
||||
beta?: boolean;
|
||||
documentation: string;
|
||||
error?: string;
|
||||
official?: boolean;
|
||||
[key: string]: Array<string> | string | APITemplateType | boolean | undefined;
|
||||
flow?: FlowType;
|
||||
[key: string]:
|
||||
| Array<string>
|
||||
| string
|
||||
| APITemplateType
|
||||
| boolean
|
||||
| FlowType
|
||||
| CustomFieldsType
|
||||
| boolean
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export type TemplateVariableType = {
|
||||
|
|
@ -27,8 +42,11 @@ export type TemplateVariableType = {
|
|||
placeholder?: string;
|
||||
list: boolean;
|
||||
show: boolean;
|
||||
readonly: boolean;
|
||||
multiline?: boolean;
|
||||
value?: any;
|
||||
dynamic?: boolean;
|
||||
proxy?: { id: string; field: string };
|
||||
input_types?: Array<string>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
|
@ -39,7 +57,7 @@ export type sendAllProps = {
|
|||
description: string;
|
||||
viewport: Viewport;
|
||||
inputs: { text?: string };
|
||||
|
||||
chatKey: string;
|
||||
chatHistory: { message: string | object; isSend: boolean }[];
|
||||
};
|
||||
export type errorsTypeAPI = {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ export type ChatMessageType = {
|
|||
isSend: boolean;
|
||||
thought?: string;
|
||||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
prompt?: string;
|
||||
chatKey: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { ReactElement, ReactNode } from "react";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
|
||||
import { ChatMessageType } from "../chat";
|
||||
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
|
||||
import { typesContextType } from "../typesContext";
|
||||
|
||||
import { sourceHandleType, targetHandleType } from "./../flow/index";
|
||||
export type InputComponentType = {
|
||||
autoFocus?: boolean;
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
|
|
@ -17,12 +19,14 @@ export type InputComponentType = {
|
|||
showPass?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
blurOnEnter?: boolean;
|
||||
};
|
||||
export type ToggleComponentType = {
|
||||
enabled: boolean;
|
||||
setEnabled: (state: boolean) => void;
|
||||
disabled: boolean | undefined;
|
||||
size: "small" | "medium" | "large";
|
||||
id?: string;
|
||||
};
|
||||
export type DropDownComponentType = {
|
||||
value: string;
|
||||
|
|
@ -36,7 +40,7 @@ export type ParameterComponentType = {
|
|||
data: NodeDataType;
|
||||
setData: (value: NodeDataType) => void;
|
||||
title: string;
|
||||
id: string;
|
||||
id: sourceHandleType | targetHandleType;
|
||||
color: string;
|
||||
left: boolean;
|
||||
type: string | undefined;
|
||||
|
|
@ -46,6 +50,7 @@ export type ParameterComponentType = {
|
|||
dataContext?: typesContextType;
|
||||
optionalHandle?: Array<String> | null;
|
||||
info?: string;
|
||||
proxy?: { field: string; id: string };
|
||||
showNode?: boolean;
|
||||
};
|
||||
export type InputListComponentType = {
|
||||
|
|
@ -61,6 +66,8 @@ export type KeyPairListComponentType = {
|
|||
disabled: boolean;
|
||||
editNode?: boolean;
|
||||
duplicateKey?: boolean;
|
||||
advanced?: boolean | null;
|
||||
dataValue?: any;
|
||||
};
|
||||
|
||||
export type DictComponentType = {
|
||||
|
|
@ -78,6 +85,7 @@ export type TextAreaComponentType = {
|
|||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
editNode?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export type PromptAreaComponentType = {
|
||||
|
|
@ -87,6 +95,7 @@ export type PromptAreaComponentType = {
|
|||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
readonly?: boolean;
|
||||
editNode?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -98,6 +107,7 @@ export type CodeAreaComponentType = {
|
|||
nodeClass?: APIClassType;
|
||||
setNodeClass?: (value: APIClassType) => void;
|
||||
dynamic?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export type FileComponentType = {
|
||||
|
|
@ -424,6 +434,7 @@ export type nodeToolbarPropsType = {
|
|||
data: NodeDataType;
|
||||
deleteNode: (idx: string) => void;
|
||||
setData: (newState: NodeDataType) => void;
|
||||
position: XYPosition;
|
||||
setShowNode: (boolean: any) => void;
|
||||
numberOfHandles: boolean[] | [];
|
||||
showNode: boolean;
|
||||
|
|
@ -459,6 +470,7 @@ export type codeAreaModalPropsType = {
|
|||
setNodeClass: (Class: APIClassType) => void | undefined;
|
||||
children: ReactNode;
|
||||
dynamic?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export type chatMessagePropsType = {
|
||||
|
|
@ -483,6 +495,7 @@ export type genericModalPropsType = {
|
|||
nodeClass?: APIClassType;
|
||||
setNodeClass?: (Class: APIClassType) => void;
|
||||
children: ReactNode;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export type buttonBoxPropsType = {
|
||||
|
|
@ -549,7 +562,7 @@ export type codeTabsPropsType = {
|
|||
) => string;
|
||||
buildTweakObject?: (
|
||||
tw: string,
|
||||
changes: string | string[] | boolean | number,
|
||||
changes: string | string[] | boolean | number | Object[] | Object,
|
||||
template: TemplateVariableType
|
||||
) => string | void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ export type NodeType = {
|
|||
type?: string;
|
||||
position: XYPosition;
|
||||
data: NodeDataType;
|
||||
selected?: boolean;
|
||||
};
|
||||
|
||||
export type NodeDataType = {
|
||||
type: string;
|
||||
node?: APIClassType;
|
||||
id: string;
|
||||
output_types?: string[];
|
||||
};
|
||||
// FlowStyleType is the type of the style object that is used to style the
|
||||
// Flow card with an emoji and a color.
|
||||
|
|
@ -36,3 +38,18 @@ export type TweaksType = Array<
|
|||
};
|
||||
} & FlowStyleType
|
||||
>;
|
||||
|
||||
// right side
|
||||
export type sourceHandleType = {
|
||||
dataType: string;
|
||||
id: string;
|
||||
baseClasses: string[];
|
||||
};
|
||||
//left side
|
||||
export type targetHandleType = {
|
||||
inputTypes?: string[];
|
||||
type: string;
|
||||
fieldName: string;
|
||||
id: string;
|
||||
proxy?: { field: string; id: string };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ import { tweakType } from "../components";
|
|||
import { FlowType } from "../flow";
|
||||
|
||||
export type TabsContextType = {
|
||||
saveFlow: (flow: FlowType) => Promise<void>;
|
||||
save: () => void;
|
||||
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
|
||||
tabId: string;
|
||||
isLoading: boolean;
|
||||
setTabId: (index: string) => void;
|
||||
flows: Array<FlowType>;
|
||||
removeFlow: (id: string) => void;
|
||||
addFlow: (
|
||||
flow?: FlowType,
|
||||
newProject?: Boolean
|
||||
newProject: boolean,
|
||||
flow?: FlowType
|
||||
) => Promise<String | undefined>;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => string;
|
||||
|
|
@ -24,7 +23,7 @@ export type TabsContextType = {
|
|||
uploadFlows: () => void;
|
||||
isBuilt: boolean;
|
||||
setIsBuilt: (state: boolean) => void;
|
||||
uploadFlow: (newFlow?: boolean, file?: File) => Promise<String | undefined>;
|
||||
uploadFlow: (newFlow: boolean, file?: File) => Promise<String | undefined>;
|
||||
hardReset: () => void;
|
||||
getNodeId: (nodeType: string) => string;
|
||||
tabsState: TabsState;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const data: { [char: string]: string } = {};
|
|||
export type typesContextType = {
|
||||
reactFlowInstance: ReactFlowInstance | null;
|
||||
setReactFlowInstance: (newState: ReactFlowInstance) => void;
|
||||
deleteNode: (idx: string) => void;
|
||||
deleteNode: (idx: string | Array<string>) => void;
|
||||
types: typeof types;
|
||||
setTypes: (newState: {}) => void;
|
||||
templates: typeof template;
|
||||
|
|
@ -21,6 +21,7 @@ export type typesContextType = {
|
|||
setFetchError: (newState: boolean) => void;
|
||||
setFilterEdge: (newState) => void;
|
||||
getFilterEdge: any[];
|
||||
deleteEdge: (idx: string | Array<string>) => void;
|
||||
saveComponent: (component: NodeDataType, key: string) => void;
|
||||
deleteComponent: (id: string, key: string) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Edge, Node } from "reactflow";
|
||||
import { NodeType } from "../flow";
|
||||
import { FlowType, NodeType } from "../flow";
|
||||
|
||||
export type cleanEdgesType = {
|
||||
flow: {
|
||||
|
|
@ -11,5 +11,17 @@ export type cleanEdgesType = {
|
|||
|
||||
export type unselectAllNodesType = {
|
||||
updateNodes: (nodes: Node[]) => void;
|
||||
data: Node[] | null;
|
||||
data: Node[];
|
||||
};
|
||||
|
||||
export type updateEdgesHandleIdsType = {
|
||||
nodes: NodeType[];
|
||||
edges: Edge[];
|
||||
};
|
||||
|
||||
export type generateFlowType = { newFlow: FlowType; removedEdges: Edge[] };
|
||||
|
||||
export type findLastNodeType = {
|
||||
nodes: NodeType[];
|
||||
edges: Edge[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,17 +3,30 @@ import {
|
|||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
OnSelectionChangeParams,
|
||||
ReactFlowInstance,
|
||||
ReactFlowJsonObject,
|
||||
XYPosition,
|
||||
} from "reactflow";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { specialCharsRegex } from "../constants/constants";
|
||||
import { APITemplateType } from "../types/api";
|
||||
import { FlowType, NodeDataType, NodeType } from "../types/flow";
|
||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
||||
import {
|
||||
FlowType,
|
||||
NodeDataType,
|
||||
NodeType,
|
||||
sourceHandleType,
|
||||
targetHandleType,
|
||||
} from "../types/flow";
|
||||
import {
|
||||
cleanEdgesType,
|
||||
findLastNodeType,
|
||||
generateFlowType,
|
||||
unselectAllNodesType,
|
||||
updateEdgesHandleIdsType,
|
||||
} from "../types/utils/reactflowUtils";
|
||||
import { toNormalCase } from "./utils";
|
||||
import { toNormalCase, toTitleCase } from "./utils";
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
||||
export function cleanEdges({
|
||||
flow: { edges, nodes },
|
||||
|
|
@ -32,26 +45,30 @@ export function cleanEdges({
|
|||
const sourceHandle = edge.sourceHandle; //right
|
||||
const targetHandle = edge.targetHandle; //left
|
||||
if (targetHandle) {
|
||||
const field = targetHandle.split("|")[1];
|
||||
const id =
|
||||
(targetNode.data.node?.template[field]?.input_types?.join(";") ??
|
||||
targetNode.data.node?.template[field]?.type) +
|
||||
"|" +
|
||||
field +
|
||||
"|" +
|
||||
targetNode.data.id;
|
||||
if (id !== targetHandle) {
|
||||
const targetHandleObject: targetHandleType =
|
||||
scapeJSONParse(targetHandle);
|
||||
const field = targetHandleObject.fieldName;
|
||||
const id: targetHandleType = {
|
||||
type: targetNode.data.node!.template[field]?.type,
|
||||
fieldName: field,
|
||||
id: targetNode.data.id,
|
||||
inputTypes: targetNode.data.node!.template[field]?.input_types,
|
||||
};
|
||||
if (targetNode.data.node!.template[field]?.proxy) {
|
||||
id.proxy = targetNode.data.node!.template[field]?.proxy;
|
||||
}
|
||||
if (scapedJSONStringfy(id) !== targetHandle) {
|
||||
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||
}
|
||||
}
|
||||
if (sourceHandle) {
|
||||
const id = [
|
||||
sourceNode.data.type,
|
||||
sourceNode.data.id,
|
||||
...sourceNode.data.node?.base_classes!,
|
||||
].join("|");
|
||||
if (id !== sourceHandle) {
|
||||
newEdges = newEdges.filter((edg) => edg.id !== edge.id);
|
||||
const id: sourceHandleType = {
|
||||
id: sourceNode.data.id,
|
||||
baseClasses: sourceNode.data.node!.base_classes,
|
||||
dataType: sourceNode.data.type,
|
||||
};
|
||||
if (scapedJSONStringfy(id) !== sourceHandle) {
|
||||
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +78,7 @@ export function cleanEdges({
|
|||
|
||||
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
||||
let newNodes = _.cloneDeep(data);
|
||||
newNodes!.forEach((node: Node) => {
|
||||
newNodes.forEach((node: Node) => {
|
||||
node.selected = false;
|
||||
});
|
||||
updateNodes(newNodes!);
|
||||
|
|
@ -71,21 +88,18 @@ export function isValidConnection(
|
|||
{ source, target, sourceHandle, targetHandle }: Connection,
|
||||
reactFlowInstance: ReactFlowInstance
|
||||
) {
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
||||
if (
|
||||
targetHandle
|
||||
?.split("|")[0]
|
||||
.split(";")
|
||||
.some((target) => target === sourceHandle?.split("|")[0]) ||
|
||||
sourceHandle
|
||||
?.split("|")
|
||||
.slice(2)
|
||||
.some((target) =>
|
||||
targetHandle
|
||||
?.split("|")[0]
|
||||
.split(";")
|
||||
.some((n) => n === target)
|
||||
) ||
|
||||
targetHandle?.split("|")[0] === "str"
|
||||
targetHandleObject.inputTypes?.some(
|
||||
(n) => n === sourceHandleObject.dataType
|
||||
) ||
|
||||
sourceHandleObject.baseClasses.some(
|
||||
(t) =>
|
||||
targetHandleObject.inputTypes?.some((n) => n === t) ||
|
||||
t === targetHandleObject.type
|
||||
) ||
|
||||
targetHandleObject.type === "str"
|
||||
) {
|
||||
let targetNode = reactFlowInstance?.getNode(target!)?.data?.node;
|
||||
if (!targetNode) {
|
||||
|
|
@ -97,11 +111,11 @@ export function isValidConnection(
|
|||
return true;
|
||||
}
|
||||
} else if (
|
||||
(!targetNode.template[targetHandle?.split("|")[1]!].list &&
|
||||
(!targetNode.template[targetHandleObject.fieldName].list &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.find((e) => e.targetHandle === targetHandle)) ||
|
||||
targetNode.template[targetHandle?.split("|")[1]!].list
|
||||
targetNode.template[targetHandleObject.fieldName].list
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -152,26 +166,36 @@ export function updateIds(
|
|||
|
||||
newFlow.nodes.forEach((node: NodeType) => {
|
||||
// Generate a unique node ID
|
||||
let newId = getNodeId(node.data.type);
|
||||
let newId = getNodeId(node.data.node?.flow ? "GroupNode" : node.data.type);
|
||||
idsMap[node.id] = newId;
|
||||
node.id = newId;
|
||||
node.data.id = newId;
|
||||
// Add the new node to the list of nodes in state
|
||||
});
|
||||
|
||||
newFlow.edges.forEach((edge) => {
|
||||
newFlow.edges.forEach((edge: Edge) => {
|
||||
edge.source = idsMap[edge.source];
|
||||
edge.target = idsMap[edge.target];
|
||||
let sourceHandleSplitted = edge.sourceHandle!.split("|");
|
||||
edge.sourceHandle =
|
||||
sourceHandleSplitted[0] +
|
||||
"|" +
|
||||
edge.source +
|
||||
"|" +
|
||||
sourceHandleSplitted.slice(2).join("|");
|
||||
let targetHandleSplitted = edge.targetHandle!.split("|");
|
||||
edge.targetHandle =
|
||||
targetHandleSplitted.slice(0, -1).join("|") + "|" + edge.target;
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!
|
||||
);
|
||||
edge.sourceHandle = scapedJSONStringfy({
|
||||
...sourceHandleObject,
|
||||
id: edge.source,
|
||||
});
|
||||
if (edge.data?.sourceHandle?.id) {
|
||||
edge.data.sourceHandle.id = edge.source;
|
||||
}
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!
|
||||
);
|
||||
edge.targetHandle = scapedJSONStringfy({
|
||||
...targetHandleObject,
|
||||
id: edge.target,
|
||||
});
|
||||
if (edge.data?.targetHandle?.id) {
|
||||
edge.data.targetHandle.id = edge.target;
|
||||
}
|
||||
edge.id =
|
||||
"reactflow__edge-" +
|
||||
edge.source +
|
||||
|
|
@ -189,10 +213,7 @@ export function buildTweaks(flow: FlowType) {
|
|||
}, {});
|
||||
}
|
||||
|
||||
export function validateNode(
|
||||
node: NodeType,
|
||||
reactFlowInstance: ReactFlowInstance
|
||||
): Array<string> {
|
||||
export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
||||
if (!node.data?.node?.template || !Object.keys(node.data.node.template)) {
|
||||
return [
|
||||
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
|
||||
|
|
@ -211,13 +232,13 @@ export function validateNode(
|
|||
(template[t].value === undefined ||
|
||||
template[t].value === null ||
|
||||
template[t].value === "") &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.some(
|
||||
(edge) =>
|
||||
edge.targetHandle?.split("|")[1] === t &&
|
||||
edge.targetHandle.split("|")[2] === node.id
|
||||
)
|
||||
!edges.some(
|
||||
(edge) =>
|
||||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
|
||||
t &&
|
||||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
|
||||
node.id
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
`${type} is missing ${
|
||||
|
|
@ -249,15 +270,13 @@ export function validateNode(
|
|||
}, [] as string[]);
|
||||
}
|
||||
|
||||
export function validateNodes(reactFlowInstance: ReactFlowInstance) {
|
||||
if (reactFlowInstance.getNodes().length === 0) {
|
||||
export function validateNodes(nodes: Node[], edges: Edge[]) {
|
||||
if (nodes.length === 0) {
|
||||
return [
|
||||
"No nodes found in the flow. Please add at least one node to the flow.",
|
||||
];
|
||||
}
|
||||
return reactFlowInstance
|
||||
.getNodes()
|
||||
.flatMap((n: NodeType) => validateNode(n, reactFlowInstance));
|
||||
return nodes.flatMap((n: NodeType) => validateNode(n, edges));
|
||||
}
|
||||
|
||||
export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
||||
|
|
@ -273,6 +292,49 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
|||
return newName;
|
||||
}
|
||||
|
||||
export function updateEdgesHandleIds({
|
||||
edges,
|
||||
nodes,
|
||||
}: updateEdgesHandleIdsType): Edge[] {
|
||||
let newEdges = _.cloneDeep(edges);
|
||||
newEdges.forEach((edge) => {
|
||||
const sourceNodeId = edge.source;
|
||||
const targetNodeId = edge.target;
|
||||
const sourceNode = nodes.find((node) => node.id === sourceNodeId);
|
||||
const targetNode = nodes.find((node) => node.id === targetNodeId);
|
||||
let source = edge.sourceHandle;
|
||||
let target = edge.targetHandle;
|
||||
//right
|
||||
let newSource: sourceHandleType;
|
||||
//left
|
||||
let newTarget: targetHandleType;
|
||||
if (target && targetNode) {
|
||||
let field = target.split("|")[1];
|
||||
newTarget = {
|
||||
type: targetNode.data.node!.template[field].type,
|
||||
fieldName: field,
|
||||
id: targetNode.data.id,
|
||||
inputTypes: targetNode.data.node!.template[field].input_types,
|
||||
};
|
||||
}
|
||||
if (source && sourceNode) {
|
||||
newSource = {
|
||||
id: sourceNode.data.id,
|
||||
baseClasses: sourceNode.data.node!.base_classes,
|
||||
dataType: sourceNode.data.type,
|
||||
};
|
||||
}
|
||||
edge.sourceHandle = scapedJSONStringfy(newSource!);
|
||||
edge.targetHandle = scapedJSONStringfy(newTarget!);
|
||||
const newData = {
|
||||
sourceHandle: scapeJSONParse(edge.sourceHandle),
|
||||
targetHandle: scapeJSONParse(edge.targetHandle),
|
||||
};
|
||||
edge.data = newData;
|
||||
});
|
||||
return newEdges;
|
||||
}
|
||||
|
||||
export function handleKeyDown(
|
||||
e:
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
|
|
@ -311,20 +373,39 @@ export function getConnectedNodes(
|
|||
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
|
||||
}
|
||||
|
||||
export function convertObjToArray(singleObject) {
|
||||
export function convertObjToArray(singleObject: object | string) {
|
||||
if (typeof singleObject === "string") {
|
||||
singleObject = JSON.parse(singleObject);
|
||||
}
|
||||
if (Array.isArray(singleObject)) return singleObject;
|
||||
|
||||
let arrConverted: any = [];
|
||||
for (const key in singleObject) {
|
||||
if (singleObject.hasOwnProperty(key)) {
|
||||
const newObj = {};
|
||||
newObj[key] = singleObject[key];
|
||||
arrConverted.push(newObj);
|
||||
let arrConverted: any[] = [];
|
||||
if (typeof singleObject === "object") {
|
||||
for (const key in singleObject) {
|
||||
if (Object.prototype.hasOwnProperty.call(singleObject, key)) {
|
||||
const newObj = {};
|
||||
newObj[key] = singleObject[key];
|
||||
arrConverted.push(newObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return arrConverted;
|
||||
}
|
||||
|
||||
export function convertArrayToObj(arrayOfObjects) {
|
||||
if (!Array.isArray(arrayOfObjects)) return arrayOfObjects;
|
||||
|
||||
let objConverted = {};
|
||||
for (const obj of arrayOfObjects) {
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
objConverted[key] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return objConverted;
|
||||
}
|
||||
|
||||
export function hasDuplicateKeys(array) {
|
||||
const keys = {};
|
||||
for (const obj of array) {
|
||||
|
|
@ -366,6 +447,596 @@ export function convertValuesToNumbers(arr) {
|
|||
});
|
||||
}
|
||||
|
||||
export function scapedJSONStringfy(json: object): string {
|
||||
return customStringify(json).replace(/"/g, "œ");
|
||||
}
|
||||
export function scapeJSONParse(json: string): any {
|
||||
let parsed = json.replace(/œ/g, '"');
|
||||
return JSON.parse(parsed);
|
||||
}
|
||||
|
||||
// this function receives an array of edges and return true if any of the handles are not a json string
|
||||
export function checkOldEdgesHandles(edges: Edge[]): boolean {
|
||||
return edges.some(
|
||||
(edge) =>
|
||||
!edge.sourceHandle ||
|
||||
!edge.targetHandle ||
|
||||
!edge.sourceHandle.includes("{") ||
|
||||
!edge.targetHandle.includes("{")
|
||||
);
|
||||
}
|
||||
|
||||
export function customStringify(obj: any): string {
|
||||
if (typeof obj === "undefined") {
|
||||
return "null";
|
||||
}
|
||||
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
if (obj instanceof Date) {
|
||||
return `"${obj.toISOString()}"`;
|
||||
}
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
const arrayItems = obj.map((item) => customStringify(item)).join(",");
|
||||
return `[${arrayItems}]`;
|
||||
}
|
||||
|
||||
const keys = Object.keys(obj).sort();
|
||||
const keyValuePairs = keys.map(
|
||||
(key) => `"${key}":${customStringify(obj[key])}`
|
||||
);
|
||||
return `{${keyValuePairs.join(",")}}`;
|
||||
}
|
||||
|
||||
export function getMiddlePoint(nodes: Node[]) {
|
||||
let middlePointX = 0;
|
||||
let middlePointY = 0;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
middlePointX += node.position.x;
|
||||
middlePointY += node.position.y;
|
||||
});
|
||||
|
||||
const totalNodes = nodes.length;
|
||||
const averageX = middlePointX / totalNodes;
|
||||
const averageY = middlePointY / totalNodes;
|
||||
|
||||
return { x: averageX, y: averageY };
|
||||
}
|
||||
|
||||
export function generateFlow(
|
||||
selection: OnSelectionChangeParams,
|
||||
reactFlowInstance: ReactFlowInstance,
|
||||
name: string
|
||||
): generateFlowType {
|
||||
const newFlowData = reactFlowInstance.toObject();
|
||||
|
||||
/* remove edges that are not connected to selected nodes on both ends
|
||||
in future we can save this edges to when ungrouping reconect to the old nodes
|
||||
*/
|
||||
newFlowData.edges = selection.edges.filter(
|
||||
(edge) =>
|
||||
selection.nodes.some((node) => node.id === edge.target) &&
|
||||
selection.nodes.some((node) => node.id === edge.source)
|
||||
);
|
||||
newFlowData.nodes = selection.nodes;
|
||||
|
||||
const newFlow: FlowType = {
|
||||
data: newFlowData,
|
||||
name: name,
|
||||
description: "",
|
||||
//generating local id instead of using the id from the server, can change in the future
|
||||
id: uid(),
|
||||
};
|
||||
// filter edges that are not connected to selected nodes on both ends
|
||||
// using O(n²) aproach because the number of edges is small
|
||||
// in the future we can use a better aproach using a set
|
||||
return {
|
||||
newFlow,
|
||||
removedEdges: selection.edges.filter(
|
||||
(edge) => !newFlowData.edges.includes(edge)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function filterFlow(
|
||||
selection: OnSelectionChangeParams,
|
||||
reactFlowInstance: ReactFlowInstance
|
||||
) {
|
||||
reactFlowInstance.setNodes((nodes) =>
|
||||
nodes.filter((node) => !selection.nodes.includes(node))
|
||||
);
|
||||
reactFlowInstance.setEdges((edges) =>
|
||||
edges.filter((edge) => !selection.edges.includes(edge))
|
||||
);
|
||||
}
|
||||
|
||||
export function findLastNode({ nodes, edges }: findLastNodeType) {
|
||||
/*
|
||||
this function receives a flow and return the last node
|
||||
*/
|
||||
let lastNode = nodes.find((n) => !edges.some((e) => e.source === n.id));
|
||||
return lastNode;
|
||||
}
|
||||
|
||||
export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
|
||||
const middlePoint = getMiddlePoint(flow.data!.nodes);
|
||||
let deltaPosition = {
|
||||
x: NewPosition.x - middlePoint.x,
|
||||
y: NewPosition.y - middlePoint.y,
|
||||
};
|
||||
flow.data!.nodes.forEach((node) => {
|
||||
node.position.x += deltaPosition.x;
|
||||
node.position.y += deltaPosition.y;
|
||||
});
|
||||
}
|
||||
|
||||
export function concatFlows(
|
||||
flow: FlowType,
|
||||
ReactFlowInstance: ReactFlowInstance
|
||||
) {
|
||||
const { nodes, edges } = flow.data!;
|
||||
ReactFlowInstance.addNodes(nodes);
|
||||
ReactFlowInstance.addEdges(edges);
|
||||
}
|
||||
|
||||
export function validateSelection(
|
||||
selection: OnSelectionChangeParams,
|
||||
edges: Edge[]
|
||||
): Array<string> {
|
||||
//add edges to selection if selection mode selected only nodes
|
||||
if (selection.edges.length === 0) {
|
||||
selection.edges = edges;
|
||||
}
|
||||
// get only edges that are connected to the nodes in the selection
|
||||
// first creates a set of all the nodes ids
|
||||
let nodesSet = new Set(selection.nodes.map((n) => n.id));
|
||||
// then filter the edges that are connected to the nodes in the set
|
||||
let connectedEdges = selection.edges.filter(
|
||||
(e) => nodesSet.has(e.source) && nodesSet.has(e.target)
|
||||
);
|
||||
// add the edges to the selection
|
||||
selection.edges = connectedEdges;
|
||||
|
||||
let errorsArray: Array<string> = [];
|
||||
// check if there is more than one node
|
||||
if (selection.nodes.length < 2) {
|
||||
errorsArray.push("Please select more than one node");
|
||||
}
|
||||
|
||||
//check if there are two or more nodes with free outputs
|
||||
if (
|
||||
selection.nodes.filter(
|
||||
(n) => !selection.edges.some((e) => e.source === n.id)
|
||||
).length > 1
|
||||
) {
|
||||
errorsArray.push("Please select only one node with free outputs");
|
||||
}
|
||||
|
||||
// check if there is any node that does not have any connection
|
||||
if (
|
||||
selection.nodes.some(
|
||||
(node) =>
|
||||
!selection.edges.some((edge) => edge.target === node.id) &&
|
||||
!selection.edges.some((edge) => edge.source === node.id)
|
||||
)
|
||||
) {
|
||||
errorsArray.push("Please select only nodes that are connected");
|
||||
}
|
||||
return errorsArray;
|
||||
}
|
||||
function updateGroupNodeTemplate(template: APITemplateType) {
|
||||
/*this function receives a template, iterates for it's items
|
||||
updating the visibility of all basic types setting it to advanced true*/
|
||||
Object.keys(template).forEach((key) => {
|
||||
let type = template[key].type;
|
||||
let input_types = template[key].input_types;
|
||||
if (
|
||||
(type === "str" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int" ||
|
||||
type === "dict" ||
|
||||
type === "NestedDict") &&
|
||||
!template[key].required &&
|
||||
!input_types
|
||||
) {
|
||||
template[key].advanced = true;
|
||||
}
|
||||
//prevent code fields from showing on the group node
|
||||
if (type === "code") {
|
||||
template[key].show = false;
|
||||
}
|
||||
});
|
||||
return template;
|
||||
}
|
||||
export function mergeNodeTemplates({
|
||||
nodes,
|
||||
edges,
|
||||
}: {
|
||||
nodes: NodeType[];
|
||||
edges: Edge[];
|
||||
}): APITemplateType {
|
||||
/* this function receives a flow and iterate throw each node
|
||||
and merge the templates with only the visible fields
|
||||
if there are two keys with the same name in the flow, we will update the display name of each one
|
||||
to show from which node it came from
|
||||
*/
|
||||
let template: APITemplateType = {};
|
||||
nodes.forEach((node) => {
|
||||
let nodeTemplate = _.cloneDeep(node.data.node!.template);
|
||||
Object.keys(nodeTemplate)
|
||||
.filter((field_name) => field_name.charAt(0) !== "_")
|
||||
.forEach((key) => {
|
||||
if (!isHandleConnected(edges, key, nodeTemplate[key], node.id)) {
|
||||
template[key + "_" + node.id] = nodeTemplate[key];
|
||||
template[key + "_" + node.id].proxy = { id: node.id, field: key };
|
||||
if (node.type === "groupNode") {
|
||||
template[key + "_" + node.id].display_name =
|
||||
node.data.node!.flow!.name + " - " + nodeTemplate[key].name;
|
||||
} else {
|
||||
template[key + "_" + node.id].display_name =
|
||||
//data id already has the node name on it
|
||||
nodeTemplate[key].display_name
|
||||
? nodeTemplate[key].display_name
|
||||
: nodeTemplate[key].name
|
||||
? toTitleCase(nodeTemplate[key].name)
|
||||
: toTitleCase(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return template;
|
||||
}
|
||||
function isHandleConnected(
|
||||
edges: Edge[],
|
||||
key: string,
|
||||
field: TemplateVariableType,
|
||||
nodeId: string
|
||||
) {
|
||||
/*
|
||||
this function receives a flow and a handleId and check if there is a connection with this handle
|
||||
*/
|
||||
scapedJSONStringfy({ type: field.type, fieldName: key, id: nodeId });
|
||||
if (field.proxy) {
|
||||
if (
|
||||
edges.some(
|
||||
(e) =>
|
||||
e.targetHandle ===
|
||||
scapedJSONStringfy({
|
||||
type: field.type,
|
||||
fieldName: key,
|
||||
id: nodeId,
|
||||
proxy: { id: field.proxy!.id, field: field.proxy!.field },
|
||||
inputTypes: field.input_types,
|
||||
} as targetHandleType)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
edges.some(
|
||||
(e) =>
|
||||
e.targetHandle ===
|
||||
scapedJSONStringfy({
|
||||
type: field.type,
|
||||
fieldName: key,
|
||||
id: nodeId,
|
||||
inputTypes: field.input_types,
|
||||
} as targetHandleType)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function generateNodeTemplate(Flow: FlowType) {
|
||||
/*
|
||||
this function receives a flow and generate a template for the group node
|
||||
*/
|
||||
let template = mergeNodeTemplates({
|
||||
nodes: Flow.data!.nodes,
|
||||
edges: Flow.data!.edges,
|
||||
});
|
||||
updateGroupNodeTemplate(template);
|
||||
return template;
|
||||
}
|
||||
|
||||
export function generateNodeFromFlow(
|
||||
flow: FlowType,
|
||||
getNodeId: (type: string) => string
|
||||
): NodeType {
|
||||
const { nodes } = flow.data!;
|
||||
const outputNode = _.cloneDeep(findLastNode(flow.data!));
|
||||
const position = getMiddlePoint(nodes);
|
||||
let data = _.cloneDeep(flow);
|
||||
const id = getNodeId(outputNode?.data.type!);
|
||||
const newGroupNode: NodeType = {
|
||||
data: {
|
||||
id,
|
||||
type: outputNode?.data.type!,
|
||||
node: {
|
||||
output_types: outputNode!.data.node!.output_types,
|
||||
display_name: "Group",
|
||||
documentation: "",
|
||||
base_classes: outputNode!.data.node!.base_classes,
|
||||
description: "double click to edit description",
|
||||
template: generateNodeTemplate(data),
|
||||
flow: data,
|
||||
},
|
||||
},
|
||||
id,
|
||||
position,
|
||||
type: "genericNode",
|
||||
};
|
||||
return newGroupNode;
|
||||
}
|
||||
|
||||
export function connectedInputNodesOnHandle(
|
||||
nodeId: string,
|
||||
handleId: string,
|
||||
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
|
||||
) {
|
||||
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
|
||||
[];
|
||||
// return the nodes connected to the input handle of the node
|
||||
const TargetEdges = edges.filter((e) => e.target === nodeId);
|
||||
TargetEdges.forEach((edge) => {
|
||||
if (edge.targetHandle === handleId) {
|
||||
const sourceNode = nodes.find((n) => n.id === edge.source);
|
||||
if (sourceNode) {
|
||||
if (sourceNode.type === "groupNode") {
|
||||
let lastNode = findLastNode(sourceNode.data.node!.flow!.data!);
|
||||
while (lastNode && lastNode.type === "groupNode") {
|
||||
lastNode = findLastNode(lastNode.data.node!.flow!.data!);
|
||||
}
|
||||
if (lastNode) {
|
||||
connectedNodes.push({
|
||||
name: sourceNode.data.node!.flow!.name,
|
||||
id: lastNode.id,
|
||||
isGroup: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
connectedNodes.push({
|
||||
name: sourceNode.data.type,
|
||||
id: sourceNode.id,
|
||||
isGroup: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return connectedNodes;
|
||||
}
|
||||
|
||||
export function ungroupNode(
|
||||
groupNode: NodeDataType,
|
||||
BaseFlow: ReactFlowJsonObject
|
||||
) {
|
||||
const { template, flow } = groupNode.node!;
|
||||
const gNodes: NodeType[] = flow!.data!.nodes;
|
||||
const gEdges = flow!.data!.edges;
|
||||
//redirect edges to correct proxy node
|
||||
let updatedEdges: Edge[] = [];
|
||||
BaseFlow.edges.forEach((edge) => {
|
||||
let newEdge = _.cloneDeep(edge);
|
||||
if (newEdge.target === groupNode.id) {
|
||||
const targetHandle: targetHandleType = newEdge.data.targetHandle;
|
||||
if (targetHandle.proxy) {
|
||||
let type = targetHandle.type;
|
||||
let field = targetHandle.proxy.field;
|
||||
let proxyId = targetHandle.proxy.id;
|
||||
let inputTypes = targetHandle.inputTypes;
|
||||
let node: NodeType = gNodes.find((n) => n.id === proxyId)!;
|
||||
if (node) {
|
||||
newEdge.target = proxyId;
|
||||
let newTargetHandle: targetHandleType = {
|
||||
fieldName: field,
|
||||
type,
|
||||
id: proxyId,
|
||||
inputTypes: inputTypes,
|
||||
};
|
||||
if (node.data.node?.flow) {
|
||||
newTargetHandle.proxy = {
|
||||
field: node.data.node.template[field].proxy?.field!,
|
||||
id: node.data.node.template[field].proxy?.id!,
|
||||
};
|
||||
}
|
||||
newEdge.data.targetHandle = newTargetHandle;
|
||||
newEdge.targetHandle = scapedJSONStringfy(newTargetHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newEdge.source === groupNode.id) {
|
||||
const lastNode = _.cloneDeep(findLastNode(flow!.data!));
|
||||
newEdge.source = lastNode!.id;
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||
newEdge.sourceHandle!
|
||||
);
|
||||
newSourceHandle.id = lastNode!.id;
|
||||
newEdge.data.sourceHandle = newSourceHandle;
|
||||
newEdge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
}
|
||||
if (edge.target === groupNode.id || edge.source === groupNode.id) {
|
||||
updatedEdges.push(newEdge);
|
||||
}
|
||||
});
|
||||
//update template values
|
||||
Object.keys(template).forEach((key) => {
|
||||
let { field, id } = template[key].proxy!;
|
||||
let nodeIndex = gNodes.findIndex((n) => n.id === id);
|
||||
if (nodeIndex !== -1) {
|
||||
let display_name: string;
|
||||
let show = gNodes[nodeIndex].data.node!.template[field].show;
|
||||
let advanced = gNodes[nodeIndex].data.node!.template[field].advanced;
|
||||
if (gNodes[nodeIndex].data.node!.template[field].display_name) {
|
||||
display_name =
|
||||
gNodes[nodeIndex].data.node!.template[field].display_name;
|
||||
} else {
|
||||
display_name = gNodes[nodeIndex].data.node!.template[field].name;
|
||||
}
|
||||
gNodes[nodeIndex].data.node!.template[field] = template[key];
|
||||
gNodes[nodeIndex].data.node!.template[field].show = show;
|
||||
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
|
||||
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
|
||||
}
|
||||
});
|
||||
|
||||
const nodes = [
|
||||
...BaseFlow.nodes.filter((n) => n.id !== groupNode.id),
|
||||
...gNodes,
|
||||
];
|
||||
const edges = [
|
||||
...BaseFlow.edges.filter(
|
||||
(e) => e.target !== groupNode.id && e.source !== groupNode.id
|
||||
),
|
||||
...gEdges,
|
||||
...updatedEdges,
|
||||
];
|
||||
BaseFlow.nodes = nodes;
|
||||
BaseFlow.edges = edges;
|
||||
}
|
||||
|
||||
export function expandGroupNode(
|
||||
groupNode: NodeDataType,
|
||||
ReactFlowInstance: ReactFlowInstance
|
||||
) {
|
||||
const { template, flow } = _.cloneDeep(groupNode.node!);
|
||||
const gNodes: NodeType[] = flow?.data?.nodes!;
|
||||
const gEdges = flow!.data!.edges;
|
||||
console.log(gEdges);
|
||||
//redirect edges to correct proxy node
|
||||
let updatedEdges: Edge[] = [];
|
||||
ReactFlowInstance.getEdges().forEach((edge) => {
|
||||
let newEdge = _.cloneDeep(edge);
|
||||
if (newEdge.target === groupNode.id) {
|
||||
const targetHandle: targetHandleType = newEdge.data.targetHandle;
|
||||
if (targetHandle.proxy) {
|
||||
let type = targetHandle.type;
|
||||
let field = targetHandle.proxy.field;
|
||||
let proxyId = targetHandle.proxy.id;
|
||||
let inputTypes = targetHandle.inputTypes;
|
||||
let node: NodeType = gNodes.find((n) => n.id === proxyId)!;
|
||||
if (node) {
|
||||
newEdge.target = proxyId;
|
||||
let newTargetHandle: targetHandleType = {
|
||||
fieldName: field,
|
||||
type,
|
||||
id: proxyId,
|
||||
inputTypes: inputTypes,
|
||||
};
|
||||
if (node.data.node?.flow) {
|
||||
newTargetHandle.proxy = {
|
||||
field: node.data.node.template[field].proxy?.field!,
|
||||
id: node.data.node.template[field].proxy?.id!,
|
||||
};
|
||||
}
|
||||
newEdge.data.targetHandle = newTargetHandle;
|
||||
newEdge.targetHandle = scapedJSONStringfy(newTargetHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newEdge.source === groupNode.id) {
|
||||
const lastNode = _.cloneDeep(findLastNode(flow!.data!));
|
||||
newEdge.source = lastNode!.id;
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||
newEdge.sourceHandle!
|
||||
);
|
||||
newSourceHandle.id = lastNode!.id;
|
||||
newEdge.data.sourceHandle = newSourceHandle;
|
||||
newEdge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
}
|
||||
if (edge.target === groupNode.id || edge.source === groupNode.id) {
|
||||
updatedEdges.push(newEdge);
|
||||
}
|
||||
});
|
||||
//update template values
|
||||
Object.keys(template).forEach((key) => {
|
||||
let { field, id } = template[key].proxy!;
|
||||
let nodeIndex = gNodes.findIndex((n) => n.id === id);
|
||||
if (nodeIndex !== -1) {
|
||||
let proxy: { id: string; field: string } | undefined;
|
||||
let display_name: string;
|
||||
let show = gNodes[nodeIndex].data.node!.template[field].show;
|
||||
let advanced = gNodes[nodeIndex].data.node!.template[field].advanced;
|
||||
if (gNodes[nodeIndex].data.node!.template[field].display_name) {
|
||||
display_name =
|
||||
gNodes[nodeIndex].data.node!.template[field].display_name;
|
||||
} else {
|
||||
display_name = gNodes[nodeIndex].data.node!.template[field].name;
|
||||
}
|
||||
if (gNodes[nodeIndex].data.node!.template[field].proxy) {
|
||||
proxy = gNodes[nodeIndex].data.node!.template[field].proxy;
|
||||
}
|
||||
gNodes[nodeIndex].data.node!.template[field] = template[key];
|
||||
gNodes[nodeIndex].data.node!.template[field].show = show;
|
||||
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
|
||||
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
|
||||
gNodes[nodeIndex].selected = false;
|
||||
if (proxy) {
|
||||
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
|
||||
} else {
|
||||
delete gNodes[nodeIndex].data.node!.template[field].proxy;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const nodes = [
|
||||
...ReactFlowInstance.getNodes().filter((n) => n.id !== groupNode.id),
|
||||
...gNodes,
|
||||
];
|
||||
const edges = [
|
||||
...ReactFlowInstance.getEdges().filter(
|
||||
(e) => e.target !== groupNode.id && e.source !== groupNode.id
|
||||
),
|
||||
...gEdges,
|
||||
...updatedEdges,
|
||||
];
|
||||
console.log(edges);
|
||||
ReactFlowInstance.setNodes(nodes);
|
||||
ReactFlowInstance.setEdges(edges);
|
||||
}
|
||||
|
||||
export function processFlow(FlowObject: ReactFlowJsonObject) {
|
||||
let clonedFLow = _.cloneDeep(FlowObject);
|
||||
clonedFLow.nodes.forEach((node: NodeType) => {
|
||||
if (node.data.node?.flow) {
|
||||
processFlow(node.data.node!.flow!.data!);
|
||||
ungroupNode(node.data, clonedFLow);
|
||||
}
|
||||
});
|
||||
return clonedFLow;
|
||||
}
|
||||
|
||||
export function getGroupStatus(
|
||||
flow: FlowType,
|
||||
ssData: { [key: string]: { valid: boolean; params: string } }
|
||||
) {
|
||||
let status = { valid: true, params: "Built sucessfully ✨" };
|
||||
const { nodes } = flow.data!;
|
||||
const ids = nodes.map((n: NodeType) => n.data.id);
|
||||
ids.forEach((id) => console.log(ssData[id]));
|
||||
ids.forEach((id) => {
|
||||
if (!ssData[id]) {
|
||||
status = ssData[id];
|
||||
return;
|
||||
}
|
||||
if (!ssData[id].valid) {
|
||||
status = { valid: false, params: ssData[id].params };
|
||||
}
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
export function createFlowComponent(nodeData: NodeDataType): FlowType {
|
||||
const flowNode: FlowType = {
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
Gift,
|
||||
GitFork,
|
||||
GithubIcon,
|
||||
Group,
|
||||
Hammer,
|
||||
HelpCircle,
|
||||
Home,
|
||||
|
|
@ -72,6 +73,7 @@ import {
|
|||
TerminalSquare,
|
||||
Trash2,
|
||||
Undo,
|
||||
Ungroup,
|
||||
Unplug,
|
||||
Upload,
|
||||
User,
|
||||
|
|
@ -314,8 +316,10 @@ export const nodeIconsLucide: iconsType = {
|
|||
UserCog2,
|
||||
Key,
|
||||
Unplug,
|
||||
BookMarked,
|
||||
Group,
|
||||
ChevronUp,
|
||||
Ungroup,
|
||||
BookMarked,
|
||||
Minus,
|
||||
Square,
|
||||
Minimize2,
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ TWEAKS = ${
|
|||
}
|
||||
|
||||
def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
|
||||
!isAuth ? `, apiKey: str=""` : ""
|
||||
!isAuth ? `, api_key: Optional[str] = None` : ""
|
||||
}) -> dict:
|
||||
"""
|
||||
Run a flow with a given message and optional tweaks.
|
||||
|
|
@ -340,18 +340,19 @@ def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
|
|||
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||
|
||||
payload = {"inputs": inputs}
|
||||
|
||||
headers = None
|
||||
if tweaks:
|
||||
payload["tweaks"] = tweaks
|
||||
${!isAuth ? 'headers = {"api-key": apiKey}' : ""}
|
||||
response = requests.post(api_url, json=payload,headers=headers)
|
||||
if api_key:
|
||||
headers = {"x-api-key": api_key}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
# Setup any tweaks you want to apply to the flow
|
||||
inputs = ${inputs}
|
||||
${!isAuth ? `api_key = "<your api key>"` : ""}
|
||||
print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS${
|
||||
!isAuth ? `, apiKey=api_key` : ""
|
||||
!isAuth ? `, api_key=api_key` : ""
|
||||
}))`;
|
||||
}
|
||||
|
||||
|
|
@ -374,8 +375,9 @@ export function getCurlCode(
|
|||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/process/${flowId} \\
|
||||
-H 'Content-Type: application/json'\\
|
||||
${!isAuth ? `-H 'api-key: <your api key>'\\` : ""}
|
||||
-H 'Content-Type: application/json'\\${
|
||||
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
|
||||
}
|
||||
-d '{"inputs": ${inputs}, "tweaks": ${
|
||||
tweak && tweak.length > 0
|
||||
? buildTweakObject(tweak)
|
||||
|
|
@ -443,10 +445,33 @@ chat_input_field: Input key that you want the chat to send the user message with
|
|||
api_key="..."`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
></langflow-chat>`;
|
||||
}
|
||||
|
||||
export function truncateLongId(id: string): string {
|
||||
let [componentName, newId] = id.split("-");
|
||||
if (componentName.length > 15) {
|
||||
componentName = componentName.slice(0, 15);
|
||||
componentName += "...";
|
||||
return componentName + "-" + newId;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function extractIdFromLongId(id: string): string {
|
||||
let [_, newId] = id.split("-");
|
||||
return newId;
|
||||
}
|
||||
|
||||
export function truncateDisplayName(name: string): string {
|
||||
if (name.length > 15) {
|
||||
name = name.slice(0, 15);
|
||||
name += "...";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function tabsArray(codes: string[], method: number) {
|
||||
if (!method) return;
|
||||
if (method === 0) {
|
||||
|
|
|
|||
8
src/frontend/start-nginx.sh
Normal file
8
src/frontend/start-nginx.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Replace the placeholder with the actual value
|
||||
sed -i "s|__BACKEND_URL__|$BACKEND_URL|g" /etc/nginx/conf.d/default.conf
|
||||
|
||||
|
||||
# Start nginx
|
||||
exec nginx -g 'daemon off;'
|
||||
20
src/frontend/tests/custom_component_full.ts
Normal file
20
src/frontend/tests/custom_component_full.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export const custom = `from langflow import CustomComponent
|
||||
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.chains import LLMChain
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.schema import Document
|
||||
from langchain.field_typing import NestedDict
|
||||
|
||||
import requests
|
||||
|
||||
class YourComponent(CustomComponent):
|
||||
display_name: str = "Custom Component"
|
||||
description: str = "Create any custom component you want!"
|
||||
|
||||
def build_config(self):
|
||||
return { "file": { "file_type": ["json"], } }
|
||||
|
||||
def build(self, url: str,file:str,integer:int,nested:NestedDict,flt:float,boolean:bool,lisst:list[str],dictionary:dict, llm: BaseLLM, prompt: PromptTemplate) -> Document:
|
||||
|
||||
return "test"`;
|
||||
20
src/frontend/tests/end-to-end/flowPage.spec.ts
Normal file
20
src/frontend/tests/end-to-end/flowPage.spec.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Page, test } from "@playwright/test";
|
||||
|
||||
test.describe("Flow Page tests", () => {
|
||||
async function goToFlowPage(page: Page) {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.getByRole("button", { name: "New Project" }).click();
|
||||
}
|
||||
|
||||
test("save", async ({ page }) => {
|
||||
await goToFlowPage(page);
|
||||
await page.getByRole("button", { name: "Custom" }).click();
|
||||
await page
|
||||
.locator("div")
|
||||
.filter({ hasText: /^Custom Component$/ })
|
||||
.nth(4)
|
||||
.dragTo(page.locator(".react-flow__pane"));
|
||||
await page.locator("div:nth-child(4) > .extra-side-bar-buttons").click();
|
||||
await page.locator(".success-alert").click();
|
||||
});
|
||||
});
|
||||
100
src/frontend/tests/end-to-end/groupNode.spec.ts
Normal file
100
src/frontend/tests/end-to-end/groupNode.spec.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("Group component tests", () => {
|
||||
test("group test", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.getByRole("button", { name: "Community Examples" }).click();
|
||||
await page
|
||||
.locator(
|
||||
"div:nth-child(7) > div:nth-child(2) > .card-component-footer-arrangement > .inline-flex"
|
||||
)
|
||||
.click();
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div/div[1]/div"
|
||||
)
|
||||
.click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[2]/div/div[1]/div"
|
||||
)
|
||||
.click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[3]/div/div[1]/div"
|
||||
)
|
||||
.click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
await page.getByRole("button", { name: "Group" }).click();
|
||||
expect(
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div"
|
||||
)
|
||||
.isVisible()
|
||||
).toBeTruthy();
|
||||
await page.getByPlaceholder("Type something...").first().click();
|
||||
await page.getByPlaceholder("Type something...").first().fill("test");
|
||||
await page.locator(".side-bar-buttons-arrangement").click();
|
||||
expect(
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div/div/div[2]/div/div/div[1]/div/div[1]/div/div"
|
||||
)
|
||||
.textContent()
|
||||
).toBe("test");
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div"
|
||||
)
|
||||
.locator('input[type="text"]')
|
||||
.click();
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div"
|
||||
)
|
||||
.locator('input[type="text"]')
|
||||
.fill("fieldValue");
|
||||
await page.locator(".side-bar-buttons-arrangement").click();
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div"
|
||||
)
|
||||
.click();
|
||||
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div"
|
||||
)
|
||||
.click();
|
||||
await page.getByLabel("Edit").click();
|
||||
await page
|
||||
.getByRole("button", { name: "zero-shot-react-description" })
|
||||
.click();
|
||||
await page.getByText("openai-functions").click();
|
||||
await page.getByRole("button", { name: "Save Changes" }).click();
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div"
|
||||
)
|
||||
.click();
|
||||
await page.getByLabel("Ungroup").click();
|
||||
await expect(
|
||||
page.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div/div/div[2]/div[3]/div/div[2]/div[4]/div/div[2]/div/input"
|
||||
)
|
||||
).toHaveValue("fieldValue");
|
||||
expect(
|
||||
await page
|
||||
.locator(
|
||||
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div/div/div[2]/div[2]/div/div[2]/div[5]/div/div[2]/div/button/span[1]"
|
||||
)
|
||||
.textContent()
|
||||
).toBe("openai-functions");
|
||||
});
|
||||
});
|
||||
563
src/frontend/tests/keypair.spec.ts
Normal file
563
src/frontend/tests/keypair.spec.ts
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("KeypairListComponent", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("csv");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sideCSVLoader"]')
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.locator('//*[@id="keypair0"]').click();
|
||||
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
|
||||
await page.locator('//*[@id="keypair100"]').click();
|
||||
await page.locator('//*[@id="keypair100"]').fill("testtesttesttesttesttest");
|
||||
|
||||
const plusButtonLocatorNode = page.locator('//*[@id="plusbtn0"]');
|
||||
const elementCountNode = await plusButtonLocatorNode.count();
|
||||
if (elementCountNode > 0) {
|
||||
await plusButtonLocatorNode.click();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="keypair1"]').click();
|
||||
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
|
||||
await page.locator('//*[@id="keypair101"]').click();
|
||||
await page.locator('//*[@id="keypair101"]').fill("testtesttesttesttesttest1");
|
||||
await page.locator('//*[@id="plusbtn1"]').click();
|
||||
|
||||
await page.locator('//*[@id="keypair2"]').click();
|
||||
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
|
||||
await page.locator('//*[@id="keypair102"]').click();
|
||||
await page.locator('//*[@id="keypair102"]').fill("testtesttesttesttesttest2");
|
||||
|
||||
await page.locator('//*[@id="minusbtn1"]').click();
|
||||
|
||||
const keyPairVerification = page.locator('//*[@id="keypair102"]');
|
||||
const elementKeyCount = await keyPairVerification.count();
|
||||
|
||||
if (elementKeyCount === 0) {
|
||||
expect(true).toBeTruthy();
|
||||
} else {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page
|
||||
.locator(
|
||||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
|
||||
await page.locator('//*[@id="showfile_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showfile_path"]').isChecked()
|
||||
).toBeFalsy();
|
||||
await page.locator('//*[@id="showmetadata"]').click();
|
||||
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
||||
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
|
||||
const elementCount = await plusButtonLocator.count();
|
||||
if (elementCount === 0) {
|
||||
expect(true).toBeTruthy();
|
||||
|
||||
await page
|
||||
.locator(
|
||||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="advancedIcon"]').click();
|
||||
await page.locator('//*[@id="editAdvancedBtn"]').click();
|
||||
|
||||
await page.locator('//*[@id="showfile_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showfile_path"]').isChecked()
|
||||
).toBeTruthy();
|
||||
await page.locator('//*[@id="showmetadata"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmetadata"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.locator('//*[@id="keypair0"]').click();
|
||||
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
|
||||
await page.locator('//*[@id="keypair100"]').click();
|
||||
await page
|
||||
.locator('//*[@id="keypair100"]')
|
||||
.fill("testtesttesttesttesttest");
|
||||
|
||||
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
|
||||
const elementCount = await plusButtonLocator.count();
|
||||
if (elementCount > 0) {
|
||||
await plusButtonLocator.click();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="keypair1"]').click();
|
||||
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
|
||||
await page.locator('//*[@id="keypair101"]').click();
|
||||
await page
|
||||
.locator('//*[@id="keypair101"]')
|
||||
.fill("testtesttesttesttesttest1");
|
||||
await page.locator('//*[@id="plusbtn1"]').click();
|
||||
|
||||
await page.locator('//*[@id="keypair2"]').click();
|
||||
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
|
||||
await page.locator('//*[@id="keypair102"]').click();
|
||||
await page
|
||||
.locator('//*[@id="keypair102"]')
|
||||
.fill("testtesttesttesttesttest2");
|
||||
|
||||
await page.locator('//*[@id="minusbtn1"]').click();
|
||||
|
||||
const keyPairVerification = page.locator('//*[@id="keypair102"]');
|
||||
const elementKeyCount = await keyPairVerification.count();
|
||||
|
||||
if (elementKeyCount === 0) {
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
||||
const key1 = await page.locator('//*[@id="keypair0"]').inputValue();
|
||||
const value1 = await page.locator('//*[@id="keypair100"]').inputValue();
|
||||
const key2 = await page.locator('//*[@id="keypair1"]').inputValue();
|
||||
const value2 = await page.locator('//*[@id="keypair101"]').inputValue();
|
||||
|
||||
if (
|
||||
key1 === "testtesttesttest" &&
|
||||
value1 === "testtesttesttesttesttest" &&
|
||||
key2 === "testtesttesttest2" &&
|
||||
value2 === "testtesttesttesttesttest2"
|
||||
) {
|
||||
expect(true).toBeTruthy();
|
||||
} else {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test("FloatComponent", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("llamacpp");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="sideLlamaCpp"]')
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.locator('//*[@id="float-input"]').click();
|
||||
await page.locator('//*[@id="float-input"]').fill("3");
|
||||
|
||||
let value = await page.locator('//*[@id="float-input"]').inputValue();
|
||||
|
||||
if (value != "2") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="float-input"]').click();
|
||||
await page.locator('//*[@id="float-input"]').fill("-3");
|
||||
|
||||
value = await page.locator('//*[@id="float-input"]').inputValue();
|
||||
|
||||
if (value != "-2") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page
|
||||
.locator(
|
||||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
|
||||
await page.locator('//*[@id="showcache"]').click();
|
||||
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showecho
|
||||
await page.locator('//*[@id="showecho"]').click();
|
||||
expect(await page.locator('//*[@id="showecho"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showf16_kv
|
||||
await page.locator('//*[@id="showf16_kv"]').click();
|
||||
expect(await page.locator('//*[@id="showf16_kv"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showgrammar_path
|
||||
await page.locator('//*[@id="showgrammar_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showgrammar_path"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showlast_n_tokens_size
|
||||
await page.locator('//*[@id="showlast_n_tokens_size"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlast_n_tokens_size"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showlogits_all
|
||||
await page.locator('//*[@id="showlogits_all"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlogits_all"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showlogprobs
|
||||
await page.locator('//*[@id="showlogprobs"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlogprobs"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showlora_base
|
||||
await page.locator('//*[@id="showlora_base"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlora_base"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showlora_path
|
||||
await page.locator('//*[@id="showlora_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlora_path"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmax_tokens
|
||||
await page.locator('//*[@id="showmax_tokens"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmax_tokens"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmetadata
|
||||
await page.locator('//*[@id="showmetadata"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmetadata"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmodel_kwargs
|
||||
await page.locator('//*[@id="showmodel_kwargs"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showmodel_path
|
||||
await page.locator('//*[@id="showmodel_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_path"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// shown_batch
|
||||
await page.locator('//*[@id="shown_batch"]').click();
|
||||
expect(await page.locator('//*[@id="shown_batch"]').isChecked()).toBeTruthy();
|
||||
|
||||
// shown_ctx
|
||||
await page.locator('//*[@id="shown_ctx"]').click();
|
||||
expect(await page.locator('//*[@id="shown_ctx"]').isChecked()).toBeTruthy();
|
||||
|
||||
// shown_gpu_layers
|
||||
await page.locator('//*[@id="shown_gpu_layers"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="shown_gpu_layers"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// shown_parts
|
||||
await page.locator('//*[@id="shown_parts"]').click();
|
||||
expect(await page.locator('//*[@id="shown_parts"]').isChecked()).toBeTruthy();
|
||||
|
||||
// shown_threads
|
||||
await page.locator('//*[@id="shown_threads"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="shown_threads"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showrepeat_penalty
|
||||
await page.locator('//*[@id="showrepeat_penalty"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrepeat_penalty"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showrope_freq_base
|
||||
await page.locator('//*[@id="showrope_freq_base"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrope_freq_base"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showrope_freq_scale
|
||||
await page.locator('//*[@id="showrope_freq_scale"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrope_freq_scale"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showseed
|
||||
await page.locator('//*[@id="showseed"]').click();
|
||||
expect(await page.locator('//*[@id="showseed"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showstop
|
||||
await page.locator('//*[@id="showstop"]').click();
|
||||
expect(await page.locator('//*[@id="showstop"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showstreaming
|
||||
await page.locator('//*[@id="showstreaming"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showstreaming"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showsuffix
|
||||
await page.locator('//*[@id="showsuffix"]').click();
|
||||
expect(await page.locator('//*[@id="showsuffix"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showtags
|
||||
await page.locator('//*[@id="showtags"]').click();
|
||||
expect(await page.locator('//*[@id="showtags"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showtemperature
|
||||
await page.locator('//*[@id="showtemperature"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showtemperature"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showtop_k
|
||||
await page.locator('//*[@id="showtop_k"]').click();
|
||||
expect(await page.locator('//*[@id="showtop_k"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showtop_p
|
||||
await page.locator('//*[@id="showtop_p"]').click();
|
||||
expect(await page.locator('//*[@id="showtop_p"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showuse_mlock
|
||||
await page.locator('//*[@id="showuse_mlock"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showuse_mlock"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showuse_mmap
|
||||
await page.locator('//*[@id="showuse_mmap"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showuse_mmap"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showverbose
|
||||
await page.locator('//*[@id="showverbose"]').click();
|
||||
expect(await page.locator('//*[@id="showverbose"]').isChecked()).toBeTruthy();
|
||||
|
||||
// showvocab_only
|
||||
await page.locator('//*[@id="showvocab_only"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showvocab_only"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.locator('//*[@id="showcache"]').click();
|
||||
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showecho
|
||||
await page.locator('//*[@id="showecho"]').click();
|
||||
expect(await page.locator('//*[@id="showecho"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showf16_kv
|
||||
await page.locator('//*[@id="showf16_kv"]').click();
|
||||
expect(await page.locator('//*[@id="showf16_kv"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showgrammar_path
|
||||
await page.locator('//*[@id="showgrammar_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showgrammar_path"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showlast_n_tokens_size
|
||||
await page.locator('//*[@id="showlast_n_tokens_size"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlast_n_tokens_size"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showlogits_all
|
||||
await page.locator('//*[@id="showlogits_all"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlogits_all"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showlogprobs
|
||||
await page.locator('//*[@id="showlogprobs"]').click();
|
||||
expect(await page.locator('//*[@id="showlogprobs"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showlora_base
|
||||
await page.locator('//*[@id="showlora_base"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlora_base"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showlora_path
|
||||
await page.locator('//*[@id="showlora_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showlora_path"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showmax_tokens
|
||||
await page.locator('//*[@id="showmax_tokens"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmax_tokens"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showmetadata
|
||||
await page.locator('//*[@id="showmetadata"]').click();
|
||||
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showmodel_kwargs
|
||||
await page.locator('//*[@id="showmodel_kwargs"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showmodel_path
|
||||
await page.locator('//*[@id="showmodel_path"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showmodel_path"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// shown_batch
|
||||
await page.locator('//*[@id="shown_batch"]').click();
|
||||
expect(await page.locator('//*[@id="shown_batch"]').isChecked()).toBeFalsy();
|
||||
|
||||
// shown_ctx
|
||||
await page.locator('//*[@id="shown_ctx"]').click();
|
||||
expect(await page.locator('//*[@id="shown_ctx"]').isChecked()).toBeFalsy();
|
||||
|
||||
// shown_gpu_layers
|
||||
await page.locator('//*[@id="shown_gpu_layers"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="shown_gpu_layers"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// shown_parts
|
||||
await page.locator('//*[@id="shown_parts"]').click();
|
||||
expect(await page.locator('//*[@id="shown_parts"]').isChecked()).toBeFalsy();
|
||||
|
||||
// shown_threads
|
||||
await page.locator('//*[@id="shown_threads"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="shown_threads"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showrepeat_penalty
|
||||
await page.locator('//*[@id="showrepeat_penalty"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrepeat_penalty"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showrope_freq_base
|
||||
await page.locator('//*[@id="showrope_freq_base"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrope_freq_base"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showrope_freq_scale
|
||||
await page.locator('//*[@id="showrope_freq_scale"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showrope_freq_scale"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showseed
|
||||
await page.locator('//*[@id="showseed"]').click();
|
||||
expect(await page.locator('//*[@id="showseed"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showstop
|
||||
await page.locator('//*[@id="showstop"]').click();
|
||||
expect(await page.locator('//*[@id="showstop"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showstreaming
|
||||
await page.locator('//*[@id="showstreaming"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showstreaming"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showsuffix
|
||||
await page.locator('//*[@id="showsuffix"]').click();
|
||||
expect(await page.locator('//*[@id="showsuffix"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showtags
|
||||
await page.locator('//*[@id="showtags"]').click();
|
||||
expect(await page.locator('//*[@id="showtags"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showtop_k
|
||||
await page.locator('//*[@id="showtop_k"]').click();
|
||||
expect(await page.locator('//*[@id="showtop_k"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showtop_p
|
||||
await page.locator('//*[@id="showtop_p"]').click();
|
||||
expect(await page.locator('//*[@id="showtop_p"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showuse_mlock
|
||||
await page.locator('//*[@id="showuse_mlock"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showuse_mlock"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showuse_mmap
|
||||
await page.locator('//*[@id="showuse_mmap"]').click();
|
||||
expect(await page.locator('//*[@id="showuse_mmap"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showverbose
|
||||
await page.locator('//*[@id="showverbose"]').click();
|
||||
expect(await page.locator('//*[@id="showverbose"]').isChecked()).toBeFalsy();
|
||||
|
||||
// showvocab_only
|
||||
await page.locator('//*[@id="showvocab_only"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showvocab_only"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
||||
const plusButtonLocator = page.locator('//*[@id="float-input"]');
|
||||
const elementCount = await plusButtonLocator.count();
|
||||
if (elementCount === 0) {
|
||||
expect(true).toBeTruthy();
|
||||
|
||||
await page
|
||||
.locator(
|
||||
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
|
||||
)
|
||||
.click();
|
||||
await page.locator('//*[@id="editAdvancedIcon"]').click();
|
||||
|
||||
// showtemperature
|
||||
await page.locator('//*[@id="showtemperature"]').click();
|
||||
expect(
|
||||
await page.locator('//*[@id="showtemperature"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
await page.locator('//*[@id="float-input"]').click();
|
||||
await page.locator('//*[@id="float-input"]').fill("3");
|
||||
|
||||
let value = await page.locator('//*[@id="float-input"]').inputValue();
|
||||
|
||||
if (value != "2") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
await page.locator('//*[@id="float-input"]').click();
|
||||
await page.locator('//*[@id="float-input"]').fill("-3");
|
||||
|
||||
value = await page.locator('//*[@id="float-input"]').inputValue();
|
||||
|
||||
if (value != "-2") {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
5
src/frontend/tests/test-1.spec.ts
Normal file
5
src/frontend/tests/test-1.spec.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { test } from "@playwright/test";
|
||||
|
||||
test("test", async ({ page }) => {
|
||||
// Recording...
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue