Merge remote-tracking branch 'origin/dev' into NGNMergeDev

This commit is contained in:
anovazzi1 2023-10-06 15:41:42 -03:00
commit 88d91c48d8
274 changed files with 11682 additions and 3913 deletions

View file

@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View file

@ -21,3 +21,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test-results/
/playwright-report/
/playwright/.cache/

View file

@ -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"]

File diff suppressed because one or more lines are too long

View file

@ -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;
}

View file

@ -58,7 +58,8 @@
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.2",
"react-tooltip": "^5.21.1",
"reactflow": "^11.8.3",
"react18-json-view": "^0.2.3",
"reactflow": "^11.9.2",
"rehype-mathjax": "^4.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
@ -73,6 +74,7 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@playwright/test": "^1.38.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9",
@ -1163,14 +1165,14 @@
}
},
"node_modules/@mui/base": {
"version": "5.0.0-beta.18",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.18.tgz",
"integrity": "sha512-e9ZCy/ndhyt5MTshAS3qAUy/40UiO0jX+kAo6a+XirrPJE+rrQW+mKPSI0uyp+5z4Vh+z0pvNoJ2S2gSrNz3BQ==",
"version": "5.0.0-beta.17",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.17.tgz",
"integrity": "sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@babel/runtime": "^7.22.15",
"@floating-ui/react-dom": "^2.0.2",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"@popperjs/core": "^2.11.8",
"clsx": "^2.0.0",
"prop-types": "^15.8.1"
@ -1202,25 +1204,25 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.12.tgz",
"integrity": "sha512-WZhCkKqhrXaSVBzoC6LNcVkIawS000OOt7gmnp4g9HhyvN0PSclRXc/JrkC7EwfzUAZJh+hiK2LaVsbtOpNuOg==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.11.tgz",
"integrity": "sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
}
},
"node_modules/@mui/material": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.12.tgz",
"integrity": "sha512-EelF2L46VcVqhg3KjzIGBBpOtcBgRh0MMy9Efuk6Do81QdcZsFC9RebCVAflo5jIdbHiBmxBs5/l5Q9NjONozg==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.11.tgz",
"integrity": "sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@mui/base": "5.0.0-beta.18",
"@mui/core-downloads-tracker": "^5.14.12",
"@mui/system": "^5.14.12",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/base": "5.0.0-beta.17",
"@mui/core-downloads-tracker": "^5.14.11",
"@mui/system": "^5.14.11",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"@types/react-transition-group": "^4.4.6",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
@ -1263,12 +1265,12 @@
}
},
"node_modules/@mui/private-theming": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.12.tgz",
"integrity": "sha512-TWwm+9+BgHFpoR3w04FG+IqID4ALa74A27RuKq2CEaWgxliBZB24EVeI6djfjFt5t4FYmIb8BMw2ZJEir7YjLQ==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.11.tgz",
"integrity": "sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/utils": "^5.14.11",
"prop-types": "^15.8.1"
},
"engines": {
@ -1289,11 +1291,11 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.12.tgz",
"integrity": "sha512-bocxt1nDmXfB3gpLfCCmFCyJ7sVmscFs+PuheO210QagZwHVp47UIRT1AiswLDYSQo1ZqmVGn7KLEJEYK0d4Xw==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.11.tgz",
"integrity": "sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@babel/runtime": "^7.22.15",
"@emotion/cache": "^11.11.0",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
@ -1320,15 +1322,15 @@
}
},
"node_modules/@mui/system": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.12.tgz",
"integrity": "sha512-6DXfjjLhW0/ia5qU3Crke7j+MnfDbMBOHlLIrqbrEqNs0AuSBv8pXniEGb+kqO0H804NJreRTEJRjCngwOX5CA==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.11.tgz",
"integrity": "sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@mui/private-theming": "^5.14.12",
"@mui/styled-engine": "^5.14.12",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/private-theming": "^5.14.11",
"@mui/styled-engine": "^5.14.11",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
@ -1380,12 +1382,12 @@
}
},
"node_modules/@mui/utils": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.12.tgz",
"integrity": "sha512-RFNXnhKQlzIkIUig6mmv0r5VbtjPdWoaBPYicq25LETdZux59HAqoRdWw15T7lp3c7gXOoE8y67+hTB8C64m2g==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.11.tgz",
"integrity": "sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==",
"dependencies": {
"@babel/runtime": "^7.23.1",
"@types/prop-types": "^15.7.7",
"@babel/runtime": "^7.22.15",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
},
@ -1438,6 +1440,21 @@
"node": ">= 8"
}
},
"node_modules/@playwright/test": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
"dev": true,
"dependencies": {
"playwright": "1.38.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@ -2984,9 +3001,9 @@
}
},
"node_modules/@swc/core": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.92.tgz",
"integrity": "sha512-vx0vUrf4YTEw59njOJ46Ha5i0cZTMYdRHQ7KXU29efN1MxcmJH2RajWLPlvQarOP1ab9iv9cApD7SMchDyx2vA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.91.tgz",
"integrity": "sha512-r950d0fdlZ8qbSDyvApn3HyCojiZE8xpgJzQvypeMi32dalYwugdJKWyLB55JIGMRGJ8+lmVvY4MPGkSR3kXgA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@ -3001,16 +3018,16 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.3.92",
"@swc/core-darwin-x64": "1.3.92",
"@swc/core-linux-arm-gnueabihf": "1.3.92",
"@swc/core-linux-arm64-gnu": "1.3.92",
"@swc/core-linux-arm64-musl": "1.3.92",
"@swc/core-linux-x64-gnu": "1.3.92",
"@swc/core-linux-x64-musl": "1.3.92",
"@swc/core-win32-arm64-msvc": "1.3.92",
"@swc/core-win32-ia32-msvc": "1.3.92",
"@swc/core-win32-x64-msvc": "1.3.92"
"@swc/core-darwin-arm64": "1.3.91",
"@swc/core-darwin-x64": "1.3.91",
"@swc/core-linux-arm-gnueabihf": "1.3.91",
"@swc/core-linux-arm64-gnu": "1.3.91",
"@swc/core-linux-arm64-musl": "1.3.91",
"@swc/core-linux-x64-gnu": "1.3.91",
"@swc/core-linux-x64-musl": "1.3.91",
"@swc/core-win32-arm64-msvc": "1.3.91",
"@swc/core-win32-ia32-msvc": "1.3.91",
"@swc/core-win32-x64-msvc": "1.3.91"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
@ -3022,9 +3039,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.92.tgz",
"integrity": "sha512-v7PqZUBtIF6Q5Cp48gqUiG8zQQnEICpnfNdoiY3xjQAglCGIQCjJIDjreZBoeZQZspB27lQN4eZ43CX18+2SnA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.91.tgz",
"integrity": "sha512-7kHGiQ1he5khcEeJuHDmLZPM3rRL/ith5OTmV6bOPsoHi46kLeixORW+ts1opC3tC9vu6xbk16xgX0QAJchc1w==",
"cpu": [
"arm64"
],
@ -3038,9 +3055,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.92.tgz",
"integrity": "sha512-Q3XIgQfXyxxxms3bPN+xGgvwk0TtG9l89IomApu+yTKzaIIlf051mS+lGngjnh9L0aUiCp6ICyjDLtutWP54fw==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.91.tgz",
"integrity": "sha512-8SpU18FbFpZDVzsHsAwdI1thF/picQGxq9UFxa8W+T9SDnbsqwFJv/6RqKJeJoDV6qFdl2OLjuO0OL7xrp0qnQ==",
"cpu": [
"x64"
],
@ -3054,9 +3071,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.92.tgz",
"integrity": "sha512-tnOCoCpNVXC+0FCfG84PBZJyLlz0Vfj9MQhyhCvlJz9hQmvpf8nTdKH7RHrOn8VfxtUBLdVi80dXgIFgbvl7qA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.91.tgz",
"integrity": "sha512-fOq4Cy8UbwX1yf0WB0d8hWZaIKCnPtPGguRqdXGLfwvhjZ9SIErT6PnmGTGRbQCNCIkOZWHKyTU0r8t2dN3haQ==",
"cpu": [
"arm"
],
@ -3070,9 +3087,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.92.tgz",
"integrity": "sha512-lFfGhX32w8h1j74Iyz0Wv7JByXIwX11OE9UxG+oT7lG0RyXkF4zKyxP8EoxfLrDXse4Oop434p95e3UNC3IfCw==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.91.tgz",
"integrity": "sha512-fki4ioRP/Esy4vdp8T34RCV+V9dqkRmOt763pf74pdiyFV2dPLXa5lnw/XvR1RTfPGknrYgjEQLCfZlReTryRw==",
"cpu": [
"arm64"
],
@ -3086,9 +3103,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.92.tgz",
"integrity": "sha512-rOZtRcLj57MSAbiecMsqjzBcZDuaCZ8F6l6JDwGkQ7u1NYR57cqF0QDyU7RKS1Jq27Z/Vg21z5cwqoH5fLN+Sg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.91.tgz",
"integrity": "sha512-XrG+DUUqNtfVLcJ20imby7fpBwQNG5VsEQBzQndSonPyUOa2YkTbBb60YDondfQGDABopuHH8gHN8o2H2/VCnQ==",
"cpu": [
"arm64"
],
@ -3102,9 +3119,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.92.tgz",
"integrity": "sha512-qptoMGnBL6v89x/Qpn+l1TH1Y0ed+v0qhNfAEVzZvCvzEMTFXphhlhYbDdpxbzRmCjH6GOGq7Y+xrWt9T1/ARg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.91.tgz",
"integrity": "sha512-d11bYhX+YPBr/Frcjc6eVn3C0LuS/9U1Li9EmQ+6s9EpYtYRl2ygSlC8eueLbaiazBnCVYFnc8bU4o0kc5B9sw==",
"cpu": [
"x64"
],
@ -3118,9 +3135,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.92.tgz",
"integrity": "sha512-g2KrJ43bZkCZHH4zsIV5ErojuV1OIpUHaEyW1gf7JWKaFBpWYVyubzFPvPkjcxHGLbMsEzO7w/NVfxtGMlFH/Q==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.91.tgz",
"integrity": "sha512-2SRp5Dke2P4jCQePkDx9trkkTstnRpZJVw5r3jvYdk0zeO6iC4+ZPvvoWXJLigqQv/fZnIiSUfJ6ssOoaEqTzQ==",
"cpu": [
"x64"
],
@ -3134,9 +3151,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.92.tgz",
"integrity": "sha512-3MCRGPAYDoQ8Yyd3WsCMc8eFSyKXY5kQLyg/R5zEqA0uthomo0m0F5/fxAJMZGaSdYkU1DgF73ctOWOf+Z/EzQ==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.91.tgz",
"integrity": "sha512-l9qKXikOxj42UIjbeZpz9xtBmr736jOMqInNP8mVF2/U+ws5sI8zJjcOFFtfis4ru7vWCXhB1wtltdlJYO2vGA==",
"cpu": [
"arm64"
],
@ -3150,9 +3167,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.92.tgz",
"integrity": "sha512-zqTBKQhgfWm73SVGS8FKhFYDovyRl1f5dTX1IwSKynO0qHkRCqJwauFJv/yevkpJWsI2pFh03xsRs9HncTQKSA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.91.tgz",
"integrity": "sha512-+s+52O0QVPmzOgjEe/rcb0AK6q/J7EHKwAyJCu/FaYO9df5ovE0HJjSKP6HAF0dGPO5hkENrXuNGujofUH9vtQ==",
"cpu": [
"ia32"
],
@ -3166,9 +3183,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.92.tgz",
"integrity": "sha512-41bE66ddr9o/Fi1FBh0sHdaKdENPTuDpv1IFHxSg0dJyM/jX8LbkjnpdInYXHBxhcLVAPraVRrNsC4SaoPw2Pg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.91.tgz",
"integrity": "sha512-7u9HDQhjUC3Gv43EFW84dZtduWCSa4MgltK+Sp9zEGti6WXqDPu/ESjvDsQEVYTBEMEvZs/xVAXPgLVHorV5nQ==",
"cpu": [
"x64"
],
@ -3779,9 +3796,9 @@
"integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ=="
},
"node_modules/@types/react": {
"version": "18.2.25",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz",
"integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==",
"version": "18.2.24",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.24.tgz",
"integrity": "sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@ -3789,9 +3806,9 @@
}
},
"node_modules/@types/react-dom": {
"version": "18.2.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.10.tgz",
"integrity": "sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==",
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz",
"integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==",
"devOptional": true,
"dependencies": {
"@types/react": "*"
@ -4499,9 +4516,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001546",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
"integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==",
"version": "1.0.30001543",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz",
"integrity": "sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==",
"funding": [
{
"type": "opencollective",
@ -4936,9 +4953,9 @@
}
},
"node_modules/daisyui": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.2.tgz",
"integrity": "sha512-yJZ1QjHUaL+r9BkquTdzNHb7KIgAJVFh0zbOXql2Wu0r7zx5qZNLxclhjN0WLoIpY+o2h/8lqXg7ijj8oTigOw==",
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.8.3.tgz",
"integrity": "sha512-64QuR6niTv58E5YyAVBkoXQGngp04jt6XOcKGxnq1C/7r5ZE3bDuOSc4ktT7CPacTRjl5cCeNYY13VxLpZP+7A==",
"dev": true,
"dependencies": {
"colord": "^2.9",
@ -5219,9 +5236,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.543",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz",
"integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g=="
"version": "1.4.540",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.540.tgz",
"integrity": "sha512-aoCqgU6r9+o9/S7wkcSbmPRFi7OWZWiXS9rtjEd+Ouyu/Xyw5RSq2XN8s5Qp8IaFOLiRrhQCphCIjAxgG3eCAg=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@ -5695,9 +5712,9 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"optional": true,
"os": [
@ -8340,6 +8357,36 @@
"node": ">= 6"
}
},
"node_modules/playwright": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
"dev": true,
"dependencies": {
"playwright-core": "1.38.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -9018,6 +9065,14 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react18-json-view": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/react18-json-view/-/react18-json-view-0.2.5.tgz",
"integrity": "sha512-BiCWyRUCVbnaK4kfNay8crOXZnWsZ6XsnY3fwOf5C+ZaY9w9FSTawo2p+h2UG/KcDP8meZuGlkP95klfFG9GfQ==",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/reactflow": {
"version": "11.9.2",
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.9.2.tgz",
@ -10687,9 +10742,9 @@
}
},
"node_modules/vite": {
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.10.tgz",
"integrity": "sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
@ -11335,17 +11390,17 @@
}
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.3.tgz",
"integrity": "sha512-oRy+X3ZazZvLfmv6viIaQmtLOMeij1noakIsK/Y47PWYhT8glfXzQ4j0YcP5i0P0qI1A4rIB//SGROGyZhx91A==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.2.tgz",
"integrity": "sha512-qF3/vZHCrjPUX5DvPE3DPDZlh+FiAWRKlP9PI7SlW1MCk8q4vUCDqyWsbF8K41ne0Yx8eeeb0m1cypn1LqUMYQ==",
"dependencies": {
"use-sync-external-store": "1.2.0"
},
@ -12082,14 +12137,14 @@
}
},
"@mui/base": {
"version": "5.0.0-beta.18",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.18.tgz",
"integrity": "sha512-e9ZCy/ndhyt5MTshAS3qAUy/40UiO0jX+kAo6a+XirrPJE+rrQW+mKPSI0uyp+5z4Vh+z0pvNoJ2S2gSrNz3BQ==",
"version": "5.0.0-beta.17",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.17.tgz",
"integrity": "sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==",
"requires": {
"@babel/runtime": "^7.23.1",
"@babel/runtime": "^7.22.15",
"@floating-ui/react-dom": "^2.0.2",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"@popperjs/core": "^2.11.8",
"clsx": "^2.0.0",
"prop-types": "^15.8.1"
@ -12103,21 +12158,21 @@
}
},
"@mui/core-downloads-tracker": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.12.tgz",
"integrity": "sha512-WZhCkKqhrXaSVBzoC6LNcVkIawS000OOt7gmnp4g9HhyvN0PSclRXc/JrkC7EwfzUAZJh+hiK2LaVsbtOpNuOg=="
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.11.tgz",
"integrity": "sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q=="
},
"@mui/material": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.12.tgz",
"integrity": "sha512-EelF2L46VcVqhg3KjzIGBBpOtcBgRh0MMy9Efuk6Do81QdcZsFC9RebCVAflo5jIdbHiBmxBs5/l5Q9NjONozg==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.11.tgz",
"integrity": "sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==",
"requires": {
"@babel/runtime": "^7.23.1",
"@mui/base": "5.0.0-beta.18",
"@mui/core-downloads-tracker": "^5.14.12",
"@mui/system": "^5.14.12",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/base": "5.0.0-beta.17",
"@mui/core-downloads-tracker": "^5.14.11",
"@mui/system": "^5.14.11",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"@types/react-transition-group": "^4.4.6",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
@ -12134,36 +12189,36 @@
}
},
"@mui/private-theming": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.12.tgz",
"integrity": "sha512-TWwm+9+BgHFpoR3w04FG+IqID4ALa74A27RuKq2CEaWgxliBZB24EVeI6djfjFt5t4FYmIb8BMw2ZJEir7YjLQ==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.11.tgz",
"integrity": "sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==",
"requires": {
"@babel/runtime": "^7.23.1",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/utils": "^5.14.11",
"prop-types": "^15.8.1"
}
},
"@mui/styled-engine": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.12.tgz",
"integrity": "sha512-bocxt1nDmXfB3gpLfCCmFCyJ7sVmscFs+PuheO210QagZwHVp47UIRT1AiswLDYSQo1ZqmVGn7KLEJEYK0d4Xw==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.11.tgz",
"integrity": "sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==",
"requires": {
"@babel/runtime": "^7.23.1",
"@babel/runtime": "^7.22.15",
"@emotion/cache": "^11.11.0",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
}
},
"@mui/system": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.12.tgz",
"integrity": "sha512-6DXfjjLhW0/ia5qU3Crke7j+MnfDbMBOHlLIrqbrEqNs0AuSBv8pXniEGb+kqO0H804NJreRTEJRjCngwOX5CA==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.11.tgz",
"integrity": "sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==",
"requires": {
"@babel/runtime": "^7.23.1",
"@mui/private-theming": "^5.14.12",
"@mui/styled-engine": "^5.14.12",
"@mui/types": "^7.2.5",
"@mui/utils": "^5.14.12",
"@babel/runtime": "^7.22.15",
"@mui/private-theming": "^5.14.11",
"@mui/styled-engine": "^5.14.11",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.14.11",
"clsx": "^2.0.0",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
@ -12183,12 +12238,12 @@
"requires": {}
},
"@mui/utils": {
"version": "5.14.12",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.12.tgz",
"integrity": "sha512-RFNXnhKQlzIkIUig6mmv0r5VbtjPdWoaBPYicq25LETdZux59HAqoRdWw15T7lp3c7gXOoE8y67+hTB8C64m2g==",
"version": "5.14.11",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.11.tgz",
"integrity": "sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==",
"requires": {
"@babel/runtime": "^7.23.1",
"@types/prop-types": "^15.7.7",
"@babel/runtime": "^7.22.15",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
}
@ -12216,6 +12271,15 @@
"fastq": "^1.6.0"
}
},
"@playwright/test": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
"dev": true,
"requires": {
"playwright": "1.38.1"
}
},
"@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@ -13037,92 +13101,92 @@
}
},
"@swc/core": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.92.tgz",
"integrity": "sha512-vx0vUrf4YTEw59njOJ46Ha5i0cZTMYdRHQ7KXU29efN1MxcmJH2RajWLPlvQarOP1ab9iv9cApD7SMchDyx2vA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.91.tgz",
"integrity": "sha512-r950d0fdlZ8qbSDyvApn3HyCojiZE8xpgJzQvypeMi32dalYwugdJKWyLB55JIGMRGJ8+lmVvY4MPGkSR3kXgA==",
"dev": true,
"requires": {
"@swc/core-darwin-arm64": "1.3.92",
"@swc/core-darwin-x64": "1.3.92",
"@swc/core-linux-arm-gnueabihf": "1.3.92",
"@swc/core-linux-arm64-gnu": "1.3.92",
"@swc/core-linux-arm64-musl": "1.3.92",
"@swc/core-linux-x64-gnu": "1.3.92",
"@swc/core-linux-x64-musl": "1.3.92",
"@swc/core-win32-arm64-msvc": "1.3.92",
"@swc/core-win32-ia32-msvc": "1.3.92",
"@swc/core-win32-x64-msvc": "1.3.92",
"@swc/core-darwin-arm64": "1.3.91",
"@swc/core-darwin-x64": "1.3.91",
"@swc/core-linux-arm-gnueabihf": "1.3.91",
"@swc/core-linux-arm64-gnu": "1.3.91",
"@swc/core-linux-arm64-musl": "1.3.91",
"@swc/core-linux-x64-gnu": "1.3.91",
"@swc/core-linux-x64-musl": "1.3.91",
"@swc/core-win32-arm64-msvc": "1.3.91",
"@swc/core-win32-ia32-msvc": "1.3.91",
"@swc/core-win32-x64-msvc": "1.3.91",
"@swc/counter": "^0.1.1",
"@swc/types": "^0.1.5"
}
},
"@swc/core-darwin-arm64": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.92.tgz",
"integrity": "sha512-v7PqZUBtIF6Q5Cp48gqUiG8zQQnEICpnfNdoiY3xjQAglCGIQCjJIDjreZBoeZQZspB27lQN4eZ43CX18+2SnA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.91.tgz",
"integrity": "sha512-7kHGiQ1he5khcEeJuHDmLZPM3rRL/ith5OTmV6bOPsoHi46kLeixORW+ts1opC3tC9vu6xbk16xgX0QAJchc1w==",
"dev": true,
"optional": true
},
"@swc/core-darwin-x64": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.92.tgz",
"integrity": "sha512-Q3XIgQfXyxxxms3bPN+xGgvwk0TtG9l89IomApu+yTKzaIIlf051mS+lGngjnh9L0aUiCp6ICyjDLtutWP54fw==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.91.tgz",
"integrity": "sha512-8SpU18FbFpZDVzsHsAwdI1thF/picQGxq9UFxa8W+T9SDnbsqwFJv/6RqKJeJoDV6qFdl2OLjuO0OL7xrp0qnQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm-gnueabihf": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.92.tgz",
"integrity": "sha512-tnOCoCpNVXC+0FCfG84PBZJyLlz0Vfj9MQhyhCvlJz9hQmvpf8nTdKH7RHrOn8VfxtUBLdVi80dXgIFgbvl7qA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.91.tgz",
"integrity": "sha512-fOq4Cy8UbwX1yf0WB0d8hWZaIKCnPtPGguRqdXGLfwvhjZ9SIErT6PnmGTGRbQCNCIkOZWHKyTU0r8t2dN3haQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm64-gnu": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.92.tgz",
"integrity": "sha512-lFfGhX32w8h1j74Iyz0Wv7JByXIwX11OE9UxG+oT7lG0RyXkF4zKyxP8EoxfLrDXse4Oop434p95e3UNC3IfCw==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.91.tgz",
"integrity": "sha512-fki4ioRP/Esy4vdp8T34RCV+V9dqkRmOt763pf74pdiyFV2dPLXa5lnw/XvR1RTfPGknrYgjEQLCfZlReTryRw==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm64-musl": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.92.tgz",
"integrity": "sha512-rOZtRcLj57MSAbiecMsqjzBcZDuaCZ8F6l6JDwGkQ7u1NYR57cqF0QDyU7RKS1Jq27Z/Vg21z5cwqoH5fLN+Sg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.91.tgz",
"integrity": "sha512-XrG+DUUqNtfVLcJ20imby7fpBwQNG5VsEQBzQndSonPyUOa2YkTbBb60YDondfQGDABopuHH8gHN8o2H2/VCnQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-gnu": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.92.tgz",
"integrity": "sha512-qptoMGnBL6v89x/Qpn+l1TH1Y0ed+v0qhNfAEVzZvCvzEMTFXphhlhYbDdpxbzRmCjH6GOGq7Y+xrWt9T1/ARg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.91.tgz",
"integrity": "sha512-d11bYhX+YPBr/Frcjc6eVn3C0LuS/9U1Li9EmQ+6s9EpYtYRl2ygSlC8eueLbaiazBnCVYFnc8bU4o0kc5B9sw==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-musl": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.92.tgz",
"integrity": "sha512-g2KrJ43bZkCZHH4zsIV5ErojuV1OIpUHaEyW1gf7JWKaFBpWYVyubzFPvPkjcxHGLbMsEzO7w/NVfxtGMlFH/Q==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.91.tgz",
"integrity": "sha512-2SRp5Dke2P4jCQePkDx9trkkTstnRpZJVw5r3jvYdk0zeO6iC4+ZPvvoWXJLigqQv/fZnIiSUfJ6ssOoaEqTzQ==",
"dev": true,
"optional": true
},
"@swc/core-win32-arm64-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.92.tgz",
"integrity": "sha512-3MCRGPAYDoQ8Yyd3WsCMc8eFSyKXY5kQLyg/R5zEqA0uthomo0m0F5/fxAJMZGaSdYkU1DgF73ctOWOf+Z/EzQ==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.91.tgz",
"integrity": "sha512-l9qKXikOxj42UIjbeZpz9xtBmr736jOMqInNP8mVF2/U+ws5sI8zJjcOFFtfis4ru7vWCXhB1wtltdlJYO2vGA==",
"dev": true,
"optional": true
},
"@swc/core-win32-ia32-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.92.tgz",
"integrity": "sha512-zqTBKQhgfWm73SVGS8FKhFYDovyRl1f5dTX1IwSKynO0qHkRCqJwauFJv/yevkpJWsI2pFh03xsRs9HncTQKSA==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.91.tgz",
"integrity": "sha512-+s+52O0QVPmzOgjEe/rcb0AK6q/J7EHKwAyJCu/FaYO9df5ovE0HJjSKP6HAF0dGPO5hkENrXuNGujofUH9vtQ==",
"dev": true,
"optional": true
},
"@swc/core-win32-x64-msvc": {
"version": "1.3.92",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.92.tgz",
"integrity": "sha512-41bE66ddr9o/Fi1FBh0sHdaKdENPTuDpv1IFHxSg0dJyM/jX8LbkjnpdInYXHBxhcLVAPraVRrNsC4SaoPw2Pg==",
"version": "1.3.91",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.91.tgz",
"integrity": "sha512-7u9HDQhjUC3Gv43EFW84dZtduWCSa4MgltK+Sp9zEGti6WXqDPu/ESjvDsQEVYTBEMEvZs/xVAXPgLVHorV5nQ==",
"dev": true,
"optional": true
},
@ -13670,9 +13734,9 @@
"integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ=="
},
"@types/react": {
"version": "18.2.25",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz",
"integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==",
"version": "18.2.24",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.24.tgz",
"integrity": "sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@ -13680,9 +13744,9 @@
}
},
"@types/react-dom": {
"version": "18.2.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.10.tgz",
"integrity": "sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==",
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz",
"integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==",
"devOptional": true,
"requires": {
"@types/react": "*"
@ -14162,9 +14226,9 @@
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
"caniuse-lite": {
"version": "1.0.30001546",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
"integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw=="
"version": "1.0.30001543",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz",
"integrity": "sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA=="
},
"ccount": {
"version": "2.0.1",
@ -14469,9 +14533,9 @@
}
},
"daisyui": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.2.tgz",
"integrity": "sha512-yJZ1QjHUaL+r9BkquTdzNHb7KIgAJVFh0zbOXql2Wu0r7zx5qZNLxclhjN0WLoIpY+o2h/8lqXg7ijj8oTigOw==",
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.8.3.tgz",
"integrity": "sha512-64QuR6niTv58E5YyAVBkoXQGngp04jt6XOcKGxnq1C/7r5ZE3bDuOSc4ktT7CPacTRjl5cCeNYY13VxLpZP+7A==",
"dev": true,
"requires": {
"colord": "^2.9",
@ -14682,9 +14746,9 @@
}
},
"electron-to-chromium": {
"version": "1.4.543",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz",
"integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g=="
"version": "1.4.540",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.540.tgz",
"integrity": "sha512-aoCqgU6r9+o9/S7wkcSbmPRFi7OWZWiXS9rtjEd+Ouyu/Xyw5RSq2XN8s5Qp8IaFOLiRrhQCphCIjAxgG3eCAg=="
},
"emoji-regex": {
"version": "8.0.0",
@ -15018,9 +15082,9 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"optional": true
},
"function-bind": {
@ -16783,6 +16847,22 @@
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
},
"playwright": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
"playwright-core": "1.38.1"
}
},
"playwright-core": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
"dev": true
},
"postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -17173,6 +17253,12 @@
"prop-types": "^15.6.2"
}
},
"react18-json-view": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/react18-json-view/-/react18-json-view-0.2.5.tgz",
"integrity": "sha512-BiCWyRUCVbnaK4kfNay8crOXZnWsZ6XsnY3fwOf5C+ZaY9w9FSTawo2p+h2UG/KcDP8meZuGlkP95klfFG9GfQ==",
"requires": {}
},
"reactflow": {
"version": "11.9.2",
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.9.2.tgz",
@ -18323,9 +18409,9 @@
}
},
"vite": {
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.10.tgz",
"integrity": "sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==",
"requires": {
"esbuild": "^0.18.10",
"fsevents": "~2.3.2",
@ -18659,14 +18745,14 @@
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
},
"zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="
},
"zustand": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.3.tgz",
"integrity": "sha512-oRy+X3ZazZvLfmv6viIaQmtLOMeij1noakIsK/Y47PWYhT8glfXzQ4j0YcP5i0P0qI1A4rIB//SGROGyZhx91A==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.2.tgz",
"integrity": "sha512-qF3/vZHCrjPUX5DvPE3DPDZlh+FiAWRKlP9PI7SlW1MCk8q4vUCDqyWsbF8K41ne0Yx8eeeb0m1cypn1LqUMYQ==",
"requires": {
"use-sync-external-store": "1.2.0"
}

View file

@ -53,7 +53,8 @@
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.2",
"react-tooltip": "^5.21.1",
"reactflow": "^11.8.3",
"react18-json-view": "^0.2.3",
"reactflow": "^11.9.2",
"rehype-mathjax": "^4.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
@ -72,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": {
@ -95,6 +96,7 @@
},
"proxy": "http://127.0.0.1:7860",
"devDependencies": {
"@playwright/test": "^1.38.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9",

View file

@ -0,0 +1,77 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* 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",
/* 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',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* 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,
// },
});

View file

@ -3,45 +3,85 @@
@tailwind utilities;
.App {
text-align: center;
text-align: center;
}
.react-flow__node {
width: auto;
height: auto;
border-radius: auto;
min-width: inherit;
}
.App-logo {
height: 40vmin;
pointer-events: none;
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@font-face {
font-family: text-security-disc;
src: url("assets/text-security-disc.woff") format("woff");
font-family: text-security-disc;
src: url("assets/text-security-disc.woff") format("woff");
}
.json-view {
height: 370px !important;
background-color: #2c2c2c !important;
border-radius: 10px !important;
padding: 10px !important;
}
.jv-indent {
overflow-y: auto !important;
max-height: 310px !important;
border-radius: 10px;
}
.jv-indent::-webkit-scrollbar {
width: 8px !important;
height: 8px !important;
border-radius: 10px;
}
.jv-indent::-webkit-scrollbar-track {
background-color: #f1f1f1 !important;
border-radius: 10px;
}
.jv-indent::-webkit-scrollbar-thumb {
background-color: #ccc !important;
border-radius: 999px !important;
}
.jv-indent::-webkit-scrollbar-thumb:hover {
background-color: #bbb !important;
}

View file

@ -9,6 +9,7 @@ import React, {
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import DictComponent from "../../../../components/dictComponent";
import Dropdown from "../../../../components/dropdownComponent";
import FloatComponent from "../../../../components/floatComponent";
import IconComponent from "../../../../components/genericIconComponent";
@ -16,15 +17,20 @@ import InputComponent from "../../../../components/inputComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import InputListComponent from "../../../../components/inputListComponent";
import IntComponent from "../../../../components/intComponent";
import KeypairListComponent from "../../../../components/keypairListComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Button } from "../../../../components/ui/button";
import { TOOLTIP_EMPTY } from "../../../../constants/constants";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import { TabsState } from "../../../../types/tabs";
import {
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
isValidConnection,
scapedJSONStringfy,
} from "../../../../utils/reactflowUtils";
@ -49,13 +55,14 @@ export default function ParameterComponent({
optionalHandle = null,
info = "",
proxy,
showNode,
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
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;
@ -71,7 +78,9 @@ export default function ParameterComponent({
updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]);
const { reactFlowInstance } = useContext(typesContext);
const groupedEdge = useRef(null);
const { reactFlowInstance, setFilterEdge } = useContext(typesContext);
let disabled =
reactFlowInstance
?.getEdges()
@ -79,13 +88,18 @@ export default function ParameterComponent({
const { data: myData } = useContext(typesContext);
const handleOnNewValue = (newValue: string | string[] | boolean): void => {
const handleOnNewValue = (
newValue: string | string[] | boolean | Object[]
): void => {
let newData = cloneDeep(data);
newData.node!.template[name].value = newValue;
setData(newData);
// Set state to pending
//@ts-ignore
setTabsState((prev: TabsState) => {
if (!prev[tabId]) {
return prev;
}
return {
...prev,
[tabId]: {
@ -98,6 +112,8 @@ export default function ParameterComponent({
renderTooltips();
};
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
useEffect(() => {
// @ts-ignore
infoHtml.current = (
@ -112,7 +128,8 @@ export default function ParameterComponent({
}, [info]);
function renderTooltips() {
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, flow!);
groupedEdge.current = groupedObj;
if (groupedObj && groupedObj.length > 0) {
//@ts-ignore
@ -121,45 +138,54 @@ export default function ParameterComponent({
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
return (
<span
key={index}
className={classNames(
index > 0 ? "mt-2 flex items-center" : "flex items-center"
<div key={index}>
{index === 0 && (
<span>
{left
? "Avaliable input components:"
: "Avaliable output components:"}
</span>
)}
>
<div
className="h-5 w-5"
style={{
color: nodeColors[item.family],
}}
<span
key={index}
className={classNames(
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center"
)}
>
<Icon
<div
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
color: nodeColors[item.family],
}}
/>
</div>
<span className="ps-2 text-xs text-foreground">
{nodeNames[item.family] ?? "Other"}
<span className="text-xs">
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + index}>
<span>
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
>
<Icon
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
}}
/>
</div>
<span className="ps-2 text-xs text-foreground">
{nodeNames[item.family] ?? "Other"}{" "}
<span className="text-xs">
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + index}>
<span>
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
</span>
</span>
</span>
</span>
</div>
);
});
} else {
@ -172,7 +198,56 @@ export default function ParameterComponent({
renderTooltips();
}, [tooltipTitle, flow]);
return (
return !showNode ? (
left &&
(type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int" ||
type === "dict" ||
type === "NestedDict") &&
!optionalHandle ? (
<></>
) : (
<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 ? "-ml-0.5 " : "-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
ref={ref}
className="mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2"
@ -214,33 +289,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={proxy?scapedJSONStringfy({...id,proxy}):scapedJSONStringfy(id)}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
}
className={classNames(
left ? "-ml-0.5 " : "-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 ? "-ml-0.5 " : "-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>
)}
{left === true &&
@ -330,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>
@ -353,11 +440,55 @@ export default function ParameterComponent({
field_name={name}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
const clone = cloneDeep(data);
clone.node = nodeClass;
setData(clone);
}}
nodeClass={data.node}
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
onChange={(e) => {
handleOnNewValue(e);
}}
/>
</div>
) : left === true && type === "NestedDict" ? (
<div className="mt-2 w-full">
<DictComponent
disabled={disabled}
editNode={false}
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;
handleOnNewValue(newValue);
}}
/>
</div>
) : left === true && type === "dict" ? (
<div className="mt-2 w-full">
<KeypairListComponent
disabled={disabled}
editNode={false}
value={
data.node!.template[name].value?.length === 0 ||
!data.node!.template[name].value
? [{ "": "" }]
: convertObjToArray(data.node!.template[name].value)
}
duplicateKey={errorDuplicateKey}
onChange={(newValue) => {
const valueToNumbers = convertValuesToNumbers(newValue);
data.node!.template[name].value = valueToNumbers;
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
handleOnNewValue(valueToNumbers);
}}
/>
</div>
) : (

View file

@ -14,7 +14,6 @@ import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import {
cleanEdges,
getGroupStatus,
handleKeyDown,
scapedJSONStringfy,
} from "../../utils/reactflowUtils";
@ -36,7 +35,8 @@ export default function GenericNode({
const [data, setData] = useState(olddata);
const { updateFlow, flows, tabId } = useContext(TabsContext);
const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
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);
@ -46,6 +46,43 @@ export default function GenericNode({
);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
const [showNode, setShowNode] = useState<boolean>(true);
const [handles, setHandles] = useState<boolean[] | []>([]);
let numberOfInputs: boolean[] = [];
function countHandles(): void {
numberOfInputs = Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map((templateCamp) => {
const { template } = data.node!;
if (template[templateCamp].input_types) return true;
if (!template[templateCamp].show) return false;
switch (template[templateCamp].type) {
case "str":
return false;
case "bool":
return false;
case "float":
return false;
case "code":
return false;
case "prompt":
return false;
case "file":
return false;
case "int":
return false;
default:
return true;
}
});
setHandles(numberOfInputs);
}
useEffect(() => {
countHandles();
}, []);
// State for outline color
const { sseData, isBuilding } = useSSE();
useEffect(() => {
@ -67,8 +104,15 @@ export default function GenericNode({
});
updateFlow(flow);
}
countHandles();
}, [data]);
useEffect(() => {
setTimeout(() => {
updateNodeInternals(data.id);
}, 300);
}, [showNode]);
// New useEffect to watch for changes in sseData and update validation status
useEffect(() => {
const relevantData = sseData[data.id];
@ -79,7 +123,6 @@ export default function GenericNode({
setValidationStatus(null);
}
}, [sseData, data.id]);
return (
<>
<NodeToolbar>
@ -88,141 +131,257 @@ export default function GenericNode({
data={data}
setData={setData}
deleteNode={deleteNode}
setShowNode={setShowNode}
numberOfHandles={handles}
showNode={showNode}
></NodeToolbarComponent>
</NodeToolbar>
<div
className={classNames(
selected ? "border border-ring" : "border",
" transition-transform ",
showNode
? " w-96 scale-100 transform rounded-lg duration-500 ease-in-out "
: " transform-width w-26 h-26 scale-90 transform rounded-full duration-500 ",
"generic-node-div"
)}
>
{data.node?.beta && (
{data.node?.beta && showNode && (
<div className="beta-badge-wrapper">
<div className="beta-badge-content">BETA</div>
</div>
)}
<div className="generic-node-div-title">
<div className="generic-node-title-arrangement">
<IconComponent
name={data.node?.flow?"Ungroup":name}
className="generic-node-icon"
iconColor={`${nodeColors[types[data.type]]}`}
/>
<div className="generic-node-tooltip-div">
{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>
<div
className={
"generic-node-div-title " +
(!showNode
? " relative h-24 w-24 rounded-full "
: " justify-between rounded-t-lg ")
}
>
<div
className={
"generic-node-title-arrangement rounded-full" +
(!showNode && "justify-center")
}
>
<IconComponent
name={data.node?.flow ? "Ungroup" : name}
className={
"generic-node-icon " +
(!showNode && "absolute inset-x-6 h-12 w-12")
}
iconColor={`${nodeColors[types[data.type]]}`}
/>
{showNode && (
<div className="generic-node-tooltip-div">
{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 content={data.node?.display_name}>
<div
className="generic-node-tooltip-div text-primary"
onDoubleClick={() => setInputName(true)}
>
{data.node?.display_name}
</div>
</ShadTooltip>
)}
</div>
) : (
<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>
<div className="round-button-div">
<div>
<Tooltip
title={
isBuilding ? (
<span>Building...</span>
) : !validationStatus ? (
<span className="flex">
Build{" "}
<IconComponent
name="Zap"
className="mx-0.5 h-5 fill-build-trigger stroke-build-trigger stroke-1"
/>{" "}
flow to validate status.
</span>
) : (
<div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string"
? validationStatus.params
.split("\n")
.map((line: string, index: number) => (
<div key={index}>{line}</div>
))
: ""}
</div>
)
}
>
<div className="generic-node-status-position">
<div
className={classNames(
validationStatus && validationStatus.valid
? "green-status"
: "status-build-animation",
"status-div"
{!showNode && (
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map(
(templateField: string, idx) =>
data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced && (
<ParameterComponent
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={
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors[
data.node?.template[templateField].type!
] ??
nodeColors.unknown
}
title={
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(
data.node.template[templateField].name
)
: toTitleCase(templateField)
}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[
templateField
].input_types?.join("\n") ??
data.node?.template[templateField].type
}
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}
/>
)
)}
></div>
<div
className={classNames(
validationStatus && !validationStatus.valid
? "red-status"
: "status-build-animation",
"status-div"
)}
></div>
<div
className={classNames(
!validationStatus || isBuilding
? "yellow-status"
: "status-build-animation",
"status-div"
)}
></div>
</div>
</Tooltip>
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node?.output_types &&
data.node.output_types.length > 0
? data.node.output_types.join("|")
: data.type
}
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}
/>
</>
)}
</div>
{showNode && (
<div className="round-button-div">
<div>
<Tooltip
title={
isBuilding ? (
<span>Building...</span>
) : !validationStatus ? (
<span className="flex">
Build{" "}
<IconComponent
name="Zap"
className="mx-0.5 h-5 fill-build-trigger stroke-build-trigger stroke-1"
/>{" "}
flow to validate status.
</span>
) : (
<div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string"
? validationStatus.params
.split("\n")
.map((line: string, index: number) => (
<div key={index}>{line}</div>
))
: ""}
</div>
)
}
>
<div className="generic-node-status-position">
<div
className={classNames(
validationStatus && validationStatus.valid
? "green-status"
: "status-build-animation",
"status-div"
)}
></div>
<div
className={classNames(
validationStatus && !validationStatus.valid
? "red-status"
: "status-build-animation",
"status-div"
)}
></div>
<div
className={classNames(
!validationStatus || isBuilding
? "yellow-status"
: "status-build-animation",
"status-div"
)}
></div>
</div>
</Tooltip>
</div>
</div>
)}
</div>
</div>
<div className="generic-node-desc">
{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
) {
{showNode && (
<div
className={
showNode
? "generic-node-desc " +
(data.node?.description !== "" ? "py-5" : "pb-5")
: ""
}
>
{data.node?.description !== "" &&
showNode &&
data.node?.flow &&
inputDescription ? (
<Textarea
autoFocus
onBlur={() => {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
setNodeDescription(nodeDescription);
@ -230,110 +389,134 @@ export default function GenericNode({
} 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) !== "_")
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
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={
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors[
data.node?.template[templateField].type!
] ??
nodeColors.unknown
}
title={
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(
data.node.template[templateField].name
)
: toTitleCase(templateField)
}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[templateField].input_types?.join(
"\n"
) ?? data.node?.template[templateField].type
}
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}
/>
) : (
<></>
)}
</div>
))}
<div
className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center"
)}
>
{" "}
</div>
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node?.output_types && data.node.output_types.length > 0
? data.node.output_types.join("|")
: data.type
}
}}
/>
) : (
<div
className="generic-node-desc-text"
onDoubleClick={() => setInputDescription(true)}
>
{data.node?.description}
</div>
)}
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
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={
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors[data.node?.template[templateField].type!] ??
nodeColors.unknown
}
title={
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(data.node.template[templateField].name)
: toTitleCase(templateField)
}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[templateField].input_types?.join(
"\n"
) ?? data.node?.template[templateField].type
}
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}
/>
) : (
<></>
)}
</div>
))}
<div
className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center"
)}
>
{" "}
</div>
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node?.output_types && data.node.output_types.length > 0
? data.node.output_types.join("|")
: data.type
}
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
type={data.node?.base_classes.join("|")}
left={false}
/>
</>
</div>
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}
/>
</>
</div>
)}
</div>
</>
);

View file

@ -36,32 +36,21 @@ export const EditFlowSettings: React.FC<InputProps> = ({
}
if (invalidName !== undefined) {
if (!nameLists.current.includes(value)) {
setInvalidName(false);
setInvalidName!(false);
} else {
setInvalidName(true);
setInvalidName!(true);
}
if (!nameLists.current.includes(value)) {
setInvalidName!(false);
} else {
setInvalidName!(true);
}
}
if (!nameLists.current.includes(value)) {
setInvalidName!(false);
} else {
setInvalidName!(true);
}
setName(value);
setCurrentName(value);
};
const [currentName, setCurrentName] = useState(name);
const [currentDescription, setCurrentDescription] = useState(description);
useEffect(() => {
setCurrentName(name);
setCurrentDescription(description);
}, [name, description]);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setCurrentDescription(flows.find((f) => f.id === tabId).description);
setDescription(event.target.value);
};
@ -78,11 +67,11 @@ 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={currentName ?? ""}
value={name ?? ""}
placeholder="File name"
id="name"
maxLength={maxLength}
@ -97,7 +86,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
name="description"
id="description"
onChange={handleDescriptionChange}
value={currentDescription}
value={description!}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}

View file

@ -12,17 +12,15 @@ import { Button } from "../ui/button";
export default function PaginatorComponent({
pageSize = 10,
pageIndex = 0,
pageIndex = 1,
rowsCount = [10, 20, 50, 100],
totalRowsCount = 0,
paginate,
}: PaginatorComponentType) {
const [size, setPageSize] = useState(pageSize);
const [index, setPageIndex] = useState(pageIndex);
const [maxIndex, setMaxPageIndex] = useState(
Math.ceil(totalRowsCount / pageSize)
);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
setMaxPageIndex(Math.ceil(totalRowsCount / size));
@ -39,8 +37,9 @@ export default function PaginatorComponent({
onValueChange={(pageSize: string) => {
setPageSize(Number(pageSize));
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
paginate(Number(pageSize), 0);
paginate(Number(pageSize), 1);
}}
value={pageSize.toString()}
>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="10" />
@ -55,30 +54,25 @@ export default function PaginatorComponent({
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {currentPage} of {maxIndex}
Page {pageIndex} of {maxIndex}
</div>
<div className="flex items-center space-x-2">
<Button
disabled={index <= 0}
disabled={pageIndex <= 1}
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(0);
setCurrentPage(1);
paginate(size, 0);
paginate(size, 1);
}}
>
<span className="sr-only">Go to first page</span>
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
</Button>
<Button
disabled={index <= 0}
disabled={pageIndex <= 1}
onClick={() => {
if (index > 0) {
const pgIndex = size - index;
setCurrentPage(currentPage - 1);
setPageIndex(pgIndex);
paginate(size, pgIndex);
if (pageIndex > 0) {
paginate(size, pageIndex - 1);
}
}}
variant="outline"
@ -88,12 +82,9 @@ export default function PaginatorComponent({
<IconComponent name="ChevronLeft" className="h-4 w-4" />
</Button>
<Button
disabled={currentPage === maxIndex}
disabled={pageIndex === maxIndex}
onClick={() => {
const pgIndex = size + index;
setPageIndex(pgIndex);
setCurrentPage(currentPage + 1);
paginate(size, pgIndex);
paginate(size, pageIndex + 1);
}}
variant="outline"
className="h-8 w-8 p-0"
@ -102,13 +93,11 @@ export default function PaginatorComponent({
<IconComponent name="ChevronRight" className="h-4 w-4" />
</Button>
<Button
disabled={currentPage === maxIndex}
disabled={pageIndex === maxIndex}
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(maxIndex - 1);
setCurrentPage(maxIndex);
paginate(size, size);
paginate(size, maxIndex);
}}
>
<span className="sr-only">Go to last page</span>

View file

@ -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",

View file

@ -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"]) {
@ -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,130 +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
readonly={
node.data.node?.flow &&
].value = target;
return newInputList;
});
tweaks.buildTweakObject!(
node["data"]["id"],
target,
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[
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
readonly={
node.data.node?.flow &&
].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
].dynamic
? true
: false
}
disabled={false}
editNode={true}
value={
!node.data.node.template[
templateField
].value ||
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" ? (

View file

@ -0,0 +1,55 @@
import { useEffect, useRef } from "react";
import { DictComponentType } from "../../types/components";
import DictAreaModal from "../../modals/dictAreaModal";
import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
export default function DictComponent({
value,
onChange,
disabled,
editNode = false,
}: DictComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange({});
}
}, [disabled]);
useEffect(() => {
if (value) onChange(value);
}, [value]);
const ref = useRef(value);
debugger;
return (
<div
className={classNames(
value.length > 1 && editNode ? "my-1" : "",
"flex flex-col gap-3"
)}
>
{
<div className="flex w-full gap-3">
<DictAreaModal
value={ref.current}
onChange={(obj) => {
onChange(obj);
}}
>
<Input
type="text"
className={
editNode
? "input-edit-node input-disable pointer-events-none cursor-pointer"
: "input-disable pointer-events-none cursor-pointer"
}
placeholder="Click to edit your dictionary..."
/>
</DictAreaModal>
</div>
}
</div>
);
}

View file

@ -10,8 +10,8 @@ export default function FloatComponent({
editNode = false,
}: FloatComponentType): JSX.Element {
const step = 0.1;
const min = 0;
const max = 1;
const min = -2;
const max = 2;
// Clear component state
useEffect(() => {
@ -27,10 +27,10 @@ export default function FloatComponent({
step={step}
min={min}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value < min.toString()) {
if (Number(event.target.value) < min) {
event.target.value = min.toString();
}
if (event.target.value > max.toString()) {
if (Number(event.target.value) > max) {
event.target.value = max.toString();
}
}}
@ -39,7 +39,7 @@ export default function FloatComponent({
disabled={disabled}
className={editNode ? "input-edit-node" : ""}
placeholder={
editNode ? "Number 0 to 1" : "Type a number from zero to one"
editNode ? "Number -2 to 2" : "Type a number from minus two to two"
}
onChange={(event) => {
onChange(event.target.value);

View file

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

View file

@ -139,7 +139,7 @@ export default function Header(): JSX.Element {
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
gradients[gradientIndex]
(userData?.profile_image ?? gradients[gradientIndex])
}
/>
</DropdownMenuTrigger>
@ -154,6 +154,12 @@ export default function Header(): JSX.Element {
Admin Page
</DropdownMenuItem>
)}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/account/settings")}
>
Profile Settings
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {

View file

@ -18,6 +18,11 @@ export default function InputListComponent({
}
}, [disabled]);
// @TODO Recursive Character Text Splitter - the value might be in string format, whereas the InputListComponent specifically requires an array format. To ensure smooth operation and prevent potential errors, it's crucial that we handle the conversion from a string to an array with the string as its element.
if (typeof value === "string") {
value = [value];
}
return (
<div
className={classNames(

View file

@ -44,7 +44,7 @@ export default function IntComponent({
step="1"
min={min}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value < min.toString()) {
if (Number(event.target.value) < min) {
event.target.value = min.toString();
}
}}

View file

@ -0,0 +1,117 @@
import { useEffect, useRef } from "react";
import { KeyPairListComponentType } from "../../types/components";
import _ from "lodash";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
import { Input } from "../ui/input";
export default function KeypairListComponent({
value,
onChange,
disabled,
editNode = false,
duplicateKey,
advanced = false,
dataValue,
}: KeyPairListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange([""]);
}
}, [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 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 key = Object.keys(ref.current[idx])[0];
ref.current[idx][key] = newValue;
onChange(ref.current);
};
return (
<div
className={classNames(
ref.current?.length > 1 && editNode ? "mx-2 my-1" : "",
"flex h-full flex-col gap-3"
)}
>
{ref.current?.map((obj, index) => {
return Object.keys(obj).map((key, idx) => {
return (
<div key={idx} className="flex w-full gap-2">
<Input
type="text"
value={key.trim()}
className={classNames(
editNode ? "input-edit-node" : "",
duplicateKey ? "input-invalid" : ""
)}
placeholder="Type key..."
onChange={(event) => handleChangeKey(event, index)}
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Backspace") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
<Input
type="text"
value={obj[key]}
className={editNode ? "input-edit-node" : ""}
placeholder="Type a value..."
onChange={(event) =>
handleChangeValue(event.target.value, index)
}
/>
{index === ref.current.length - 1 ? (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
newInputList.push({ "": "" });
onChange(newInputList);
}}
>
<IconComponent
name="Plus"
className={"h-4 w-4 hover:text-accent-foreground"}
/>
</button>
) : (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
newInputList.splice(index, 1);
onChange(newInputList);
}}
>
<IconComponent
name="X"
className="h-4 w-4 hover:text-status-red"
/>
</button>
)}
</div>
);
});
})}
</div>
);
}

View file

@ -4,10 +4,10 @@ export default function LoadingComponent({
remSize,
}: LoadingComponentProps): JSX.Element {
return (
<div role="status" className="m-auto w-min">
<div role="status" className="flex flex-col items-center justify-center">
<svg
aria-hidden="true"
className={`w-${remSize} h-${remSize} mr-2 animate-spin fill-almost-medium-blue text-muted`}
className={`w-${remSize} h-${remSize} animate-spin fill-almost-medium-blue text-muted`}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View file

@ -3,7 +3,7 @@ import { useEffect } from "react";
import { TypeModal } from "../../constants/enums";
import { postValidatePrompt } from "../../controllers/API";
import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import { PromptAreaComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
export default function PromptAreaComponent({
@ -15,7 +15,7 @@ export default function PromptAreaComponent({
disabled,
editNode = false,
readonly = false,
}: TextAreaComponentType): JSX.Element {
}: PromptAreaComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange("");

View file

@ -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>
);

View file

@ -1,19 +1,18 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import * as React from "react";
import { cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="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(
"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}
@ -44,14 +43,14 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"noundo nocopy items-left z-50 flex w-full max-w-lg flex-col justify-center gap-4 overflow-auto border bg-background p-6 text-start shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-bottom-[48%] sm:rounded-lg md:w-full",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<IconComponent name="X" className="h-4 w-4" />
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>

View file

@ -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}
/>

View file

@ -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={() => {

View file

@ -0,0 +1,108 @@
"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import * as React from "react";
import { cn } from "../../utils/utils";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn("flex w-full items-center justify-between", className)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild></SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View file

@ -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}
/>

View file

@ -115,6 +115,9 @@ export const EDIT_DIALOG_SUBTITLE =
export const CODE_PROMPT_DIALOG_SUBTITLE =
"Edit your Python code. This code snippet accepts module import and a single function definition. Make sure that your function returns a string.";
export const CODE_DICT_DIALOG_SUBTITLE =
"Edit your dictionary. This dialog allows you to create your own customized dictionary. You can add as many key-value pairs as you want. While in edit mode, you can enter ({}) or ([]), and this will result in adding a new object or array.";
/**
* The base text for subtitle of Prompt Dialog
* @constant
@ -499,6 +502,21 @@ export const NOUNS: string[] = [
*/
export const USER_PROJECTS_HEADER = "My Collection";
/**
* Header text for admin page
* @constant
*
*/
export const ADMIN_HEADER_TITLE = "Admin Page";
/**
* Header description for admin page
* @constant
*
*/
export const ADMIN_HEADER_DESCRIPTION =
"Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.";
/**
* URLs excluded from error retries.
* @constant
@ -511,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: "",
@ -519,6 +543,12 @@ export const CONTROL_INPUT_STATE = {
username: "",
};
export const CONTROL_PATCH_USER_STATE = {
password: "",
cnfPassword: "",
gradient: "",
};
export const CONTROL_LOGIN_STATE = {
username: "",
password: "",

View file

@ -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);
}
@ -63,10 +63,13 @@ export function AuthProvider({ children }): React.ReactElement {
.then((user) => {
setUserData(user);
setLoading(false);
const isSuperUser = user.is_superuser;
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 {
}, []);
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);

View file

@ -46,7 +46,6 @@ import { typesContext } from "./typesContext";
const uid = new ShortUniqueId({ length: 5 });
const TabsContextInitialValue: TabsContextType = {
save: () => {},
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
@ -62,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: {},
@ -111,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) => {
@ -255,7 +231,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function hardReset() {
newNodeId.current = uid();
setTabId("");
setFlows([]);
setIsLoading(true);
setId(uid());
@ -616,13 +591,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);
@ -666,7 +643,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
tabId,
setTabId,
flows,
save,
incrementNodeId,
removeFlow,
addFlow,

View file

@ -5,7 +5,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 { typesContextType } from "../types/typesContext";
@ -26,6 +26,9 @@ const initialValue: typesContextType = {
setData: () => {},
setFetchError: () => {},
fetchError: false,
setFilterEdge: (filter) => {},
getFilterEdge: [],
deleteEdge: () => {},
};
export const typesContext = createContext<typesContextType>(initialValue);
@ -39,6 +42,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
const { getAuthentication } = useContext(AuthContext);
const [getFilterEdge, setFilterEdge] = useState([]);
useEffect(() => {
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
@ -89,19 +93,38 @@ 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)
)
);
}
return (
<typesContext.Provider
value={{
deleteEdge,
types,
setTypes,
reactFlowInstance,
@ -113,6 +136,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
setData,
fetchError,
setFetchError,
setFilterEdge,
getFilterEdge,
}}
>
{children}

View file

@ -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,13 +31,16 @@ function ApiInterceptor() {
logout();
navigate("/login");
}
const res = await renewAccessToken(refreshToken);
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 ${accessToken}`;
error.config.headers["Authorization"] = `Bearer ${cookies.get(
"access_tkn_lflw"
)}`;
const response = await axios.request(error.config);
return response;
}
@ -45,6 +48,10 @@ function ApiInterceptor() {
if (axios.isAxiosError(error) && error.response?.status === 401) {
logout();
navigate("/login");
} else {
console.error(error);
logout();
navigate("/login");
}
}
}

View file

@ -6,6 +6,8 @@ import {
APIObjectType,
LoginType,
Users,
changeUser,
resetPasswordType,
sendAllProps,
} from "../../types/api/index";
import { UserInputType } from "../../types/components";
@ -144,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) {
@ -393,16 +395,17 @@ export async function autoLogin() {
export async function renewAccessToken(token: string) {
try {
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
if (token) {
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function getLoggedUser(): Promise<Users> {
export async function getLoggedUser(): Promise<Users | null> {
try {
const res = await api.get(`${BASE_URL_API}user`);
const res = await api.get(`${BASE_URL_API}users/whoami`);
if (res.status === 200) {
return res.data;
@ -411,14 +414,16 @@ export async function getLoggedUser(): Promise<Users> {
console.log("Error:", error);
throw error;
}
return null;
}
export async function addUser(user: UserInputType): Promise<Users> {
export async function addUser(user: UserInputType): Promise<Array<Users>> {
try {
const res = await api.post(`${BASE_URL_API}user`, user);
if (res.status === 200) {
return res.data;
const res = await api.post(`${BASE_URL_API}users/`, user);
if (res.status !== 201) {
throw new Error(res.data.detail);
}
return res.data;
} catch (error) {
console.log("Error:", error);
throw error;
@ -428,10 +433,10 @@ export async function addUser(user: UserInputType): Promise<Users> {
export async function getUsersPage(
skip: number,
limit: number
): Promise<[Users]> {
): Promise<Array<Users>> {
try {
const res = await api.get(
`${BASE_URL_API}users?skip=${skip}&limit=${limit}`
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
);
if (res.status === 200) {
return res.data;
@ -440,11 +445,12 @@ export async function getUsersPage(
console.log("Error:", error);
throw error;
}
return [];
}
export async function deleteUser(user_id: string) {
try {
const res = await api.delete(`${BASE_URL_API}user/${user_id}`);
const res = await api.delete(`${BASE_URL_API}users/${user_id}`);
if (res.status === 200) {
return res.data;
}
@ -454,9 +460,24 @@ export async function deleteUser(user_id: string) {
}
}
export async function updateUser(user_id: string, user: Users) {
export async function updateUser(user_id: string, user: changeUser) {
try {
const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user);
const res = await api.patch(`${BASE_URL_API}users/${user_id}`, user);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function resetPassword(user_id: string, user: resetPasswordType) {
try {
const res = await api.patch(
`${BASE_URL_API}users/${user_id}/reset-password`,
user
);
if (res.status === 200) {
return res.data;
}

View file

@ -1,22 +1,21 @@
import { InfinityIcon } from "lucide-react";
import { forwardRef } from "react";
const GradientSparkles = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
return (
<>
<svg width="0" height="0" style={{ position: "absolute" }}>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop className="gradient-start" offset="0%" />
<stop className="gradient-end" offset="100%" />
</linearGradient>
</defs>
</svg>
<InfinityIcon stroke="url(#grad1)" ref={ref} {...props} />
</>
);
}
);
export default GradientSparkles;
export const GradientSparkles = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return (
<>
<svg width="0" height="0" style={{ position: "absolute" }}>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop className="gradient-start" offset="0%" />
<stop className="gradient-end" offset="100%" />
</linearGradient>
</defs>
</svg>
<InfinityIcon stroke="url(#grad1)" ref={ref} {...props} />
</>
);
});

View file

@ -14,11 +14,12 @@ import {
import CodeTabsComponent from "../../components/codeTabsComponent";
import IconComponent from "../../components/genericIconComponent";
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
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,
@ -33,23 +34,27 @@ const ApiModal = forwardRef(
{
flow,
children,
disable,
}: {
flow: FlowType;
children: ReactNode;
disable: boolean;
},
ref
) => {
const { autoLogin } = useContext(AuthContext);
const [open, setOpen] = useState(false);
const [activeTab, setActiveTab] = useState("0");
const tweak = useRef<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonApiCode = getPythonApiCode(
flow,
autoLogin,
tweak.current,
tabsState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
const tweaksCode = buildTweaks(flow);
const codesArray = [
curl_code,
@ -100,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"]);
@ -113,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") {
@ -126,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)
);
@ -152,10 +167,15 @@ const ApiModal = forwardRef(
tweak.current.push(newTweak);
}
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonApiCode = getPythonApiCode(
flow,
autoLogin,
tweak.current,
tabsState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
tabs![0].code = curl_code;
tabs![1].code = pythonApiCode;
@ -201,8 +221,8 @@ const ApiModal = forwardRef(
}
return (
<BaseModal open={open} setOpen={setOpen} disable={disable}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">Code</span>
<IconComponent

View file

@ -6,6 +6,7 @@ import BaseModal from "../baseModal";
export default function ConfirmationModal({
title,
asChild,
titleHeader,
modalContent,
modalContentTitle,
@ -22,7 +23,7 @@ export default function ConfirmationModal({
const [open, setOpen] = useState(false);
return (
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Trigger asChild={asChild}>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon

View file

@ -1,6 +1,14 @@
import { cloneDeep } from "lodash";
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
import {
ReactNode,
forwardRef,
useContext,
useEffect,
useRef,
useState,
} from "react";
import CodeAreaComponent from "../../components/codeAreaComponent";
import DictComponent from "../../components/dictComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
import IconComponent from "../../components/genericIconComponent";
@ -8,6 +16,7 @@ import InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
import KeypairListComponent from "../../components/keypairListComponent";
import PromptAreaComponent from "../../components/promptComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
@ -26,6 +35,11 @@ import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow";
import { TabsState } from "../../types/tabs";
import {
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
} from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import BaseModal from "../baseModal";
@ -36,54 +50,63 @@ const EditNodeModal = forwardRef(
setData,
nodeLength,
children,
open,
onClose,
}: {
data: NodeDataType;
setData: (data: NodeDataType) => void;
nodeLength: number;
children: ReactNode;
open?: boolean;
onClose?: (close: boolean) => void;
},
ref
) => {
const [modalOpen, setModalOpen] = useState(false);
const [myData, setMyData] = useState(data);
const [modalOpen, setModalOpen] = useState(open ?? false);
const myData = useRef(data);
const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance
?.getEdges()
.some((edge) => edge.targetHandle === data.id) ?? false;
function changeAdvanced(templateParam: string): void {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node!.template[templateParam].advanced =
!newData.node!.template[templateParam].advanced;
return newData;
});
function changeAdvanced(n) {
myData.current.node!.template[n].advanced =
!myData.current.node!.template[n].advanced;
setAdv(!adv);
}
const handleOnNewValue = (
newValue: string | string[] | boolean,
name: string
) => {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node!.template[name].value = newValue;
return newData;
});
const handleOnNewValue = (newValue: any, name) => {
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 [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
const [adv, setAdv] = useState<boolean | null>(null);
const [dataValue, setDataValue] = useState(data);
return (
<BaseModal size="large-h-full" open={modalOpen} setOpen={setModalOpen}>
<BaseModal
key={data.id}
size="large-h-full"
open={modalOpen}
setOpen={setModalOpen}
onChangeOpenModal={(open) => {
myData.current = data;
}}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={myData.node?.description!}>
<span className="pr-2">{myData.node?.display_name}</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">
@ -116,64 +139,77 @@ 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].display_name
? myData.node.template[templateParam]
{myData.current.node?.template[templateParam]
.display_name
? myData.current.node.template[templateParam]
.display_name
: myData.node?.template[templateParam].name}
: myData.current.node?.template[templateParam]
.name}
</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]
.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);
@ -184,12 +220,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);
@ -197,14 +235,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(
@ -215,76 +327,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[
@ -293,8 +413,8 @@ const EditNodeModal = forwardRef(
}}
></InputFileComponent>
</div>
) : myData.node?.template[templateParam].type ===
"prompt" ? (
) : myData.current.node?.template[templateParam]
.type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
readonly={
@ -307,21 +427,22 @@ const EditNodeModal = forwardRef(
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={
@ -342,16 +463,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>
@ -361,12 +483,13 @@ const EditNodeModal = forwardRef(
<div className="items-center text-center">
<ToggleShadComponent
enabled={
!myData.node?.template[templateParam]
.advanced
}
setEnabled={(e) =>
changeAdvanced(templateParam)
!myData.current.node?.template[
templateParam
].advanced
}
setEnabled={(e) => {
changeAdvanced(templateParam);
}}
disabled={disabled}
size="small"
/>
@ -386,7 +509,8 @@ const EditNodeModal = forwardRef(
<Button
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 {

View file

@ -23,6 +23,7 @@ export default function UserManagementModal({
data,
index,
onConfirm,
asChild,
}: UserManagementType) {
const Icon: any = nodeIconsLucide[icon];
const [pwdVisible, setPwdVisible] = useState(false);
@ -60,7 +61,7 @@ export default function UserManagementModal({
return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Trigger asChild={asChild}>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon

View file

@ -1,4 +1,4 @@
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
import React from "react";
import {
@ -14,13 +14,25 @@ import { modalHeaderType } from "../../types/components";
type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string };
type FooterProps = { children: ReactNode };
type TriggerProps = { children: ReactNode };
type TriggerProps = {
children: ReactNode;
asChild?: boolean;
disable?: boolean;
};
const Content: React.FC<ContentProps> = ({ children }) => {
return <div className="h-full w-full">{children}</div>;
};
const Trigger: React.FC<ContentProps> = ({ children }) => {
return <>{children}</>;
const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
return (
<DialogTrigger
className={asChild ? "" : "w-full"}
hidden={children ? false : true}
asChild={asChild}
>
{children}
</DialogTrigger>
);
};
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
@ -47,7 +59,6 @@ interface BaseModalProps {
];
open?: boolean;
setOpen?: (open: boolean) => void;
disable?: boolean;
size?:
| "x-small"
| "smaller"
@ -57,13 +68,16 @@ interface BaseModalProps {
| "large-h-full"
| "small-h-full"
| "medium-h-full";
disable?: boolean;
onChangeOpenModal?: (open?: boolean) => void;
}
function BaseModal({
open,
setOpen,
disable = false,
children,
size = "large",
onChangeOpenModal,
}: BaseModalProps) {
const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header
@ -117,20 +131,21 @@ function BaseModal({
break;
}
useEffect(() => {
if (onChangeOpenModal) {
onChangeOpenModal(open);
}
}, [open]);
//UPDATE COLORS AND STYLE CLASSSES
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger
className={"w-full " + (disable ? "button-disable" : "")}
hidden={triggerChild ? false : true}
>
{triggerChild}
</DialogTrigger>
{triggerChild}
<DialogContent className={minWidth}>
<div className="truncate-doubleline word-break-break-word">
{headerChild}
</div>
<div className={`mt-2 flex flex-col ${height} w-full `}>
<div className={`mt-2 flex flex-col ${height!} w-full `}>
{ContentChild}
</div>
{ContentFooter && (

View file

@ -166,18 +166,18 @@ export default function CodeAreaModal({
</div>
<div
className={
"w-full transition-all delay-500 " +
(error?.detail.error !== undefined ? "h-2/6" : "h-0")
"whitespace-break-spaces transition-all delay-500" +
(error?.detail?.error !== undefined ? "h-2/6" : "h-0")
}
>
<div className="mt-1 h-full w-full overflow-y-auto overflow-x-clip text-left custom-scroll">
<div className="mt-1 h-full max-h-[10rem] w-full overflow-y-auto overflow-x-clip text-left custom-scroll">
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<pre className="w-full word-break-break-word">
<span className="w-full word-break-break-word">
{error?.detail?.traceback}
</pre>
</span>
</div>
</div>
</div>

View file

@ -0,0 +1,71 @@
import "ace-builds/src-noconflict/ace";
import "ace-builds/src-noconflict/ext-language_tools";
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, useRef, useState } from "react";
import JsonView from "react18-json-view";
import "react18-json-view/src/dark.css";
import "react18-json-view/src/style.css";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { CODE_DICT_DIALOG_SUBTITLE } from "../../constants/constants";
import BaseModal from "../baseModal";
export default function DictAreaModal({
children,
onChange,
value,
}): JSX.Element {
const [open, setOpen] = useState(false);
const ref = useRef(value);
useEffect(() => {
if (value) ref.current = value;
}, [ref]);
return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_DICT_DIALOG_SUBTITLE}>
<span className="pr-2">Edit Dictionary</span>
<IconComponent
name="BookMarked"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full w-full flex-col transition-all ">
<JsonView
theme="vscode"
dark={true}
editable
enableClipboard
onEdit={(edit) => {
ref.current = edit["src"];
}}
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(ref.current);
setOpen(false);
}}
>
Save
</Button>
</div>
</div>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -10,15 +10,15 @@ import BaseModal from "../baseModal";
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext);
const { flows, tabId, downloadFlow } = useContext(TabsContext);
const [checked, setChecked] = useState(false);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
setName(flow.name);
setDescription(flow.description);
}, [flow.name, flow.description]);
const [name, setName] = useState(flow.name);
const [description, setDescription] = useState(flow.description);
setName(flow!.name);
setDescription(flow!.description);
}, [flow!.name, flow!.description]);
const [name, setName] = useState(flow!.name);
const [description, setDescription] = useState(flow!.description);
const [open, setOpen] = useState(false);
return (
@ -40,7 +40,6 @@ const ExportModal = forwardRef(
tabId={tabId}
setName={setName}
setDescription={setDescription}
updateFlow={updateFlow}
/>
<div className="mt-3 flex items-center space-x-2">
<Checkbox

View file

@ -27,7 +27,7 @@ import { AuthContext } from "../../contexts/authContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API";
import { TabsState } from "../../types/tabs";
import { processFlow, validateNodes } from "../../utils/reactflowUtils";
import { validateNodes } from "../../utils/reactflowUtils";
export default function FormModal({
flow,
@ -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,16 +206,26 @@ export default function FormModal({
}
function handleWsMessage(data: any) {
if (Array.isArray(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>;
@ -214,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,
@ -222,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,
}
@ -239,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({
@ -254,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 });
}
@ -282,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) {
@ -313,6 +331,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(() => {
@ -355,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) => {
@ -390,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);
}
@ -403,7 +430,6 @@ export default function FormModal({
setChatValue("");
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>

View file

@ -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({
@ -38,6 +34,7 @@ export default function GenericModal({
}: 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);
@ -90,7 +87,7 @@ export default function GenericModal({
useEffect(() => {
setInputValue(value);
}, [value]);
}, [value, modalOpen]);
const coloredContent = (inputValue || "")
.replace(/</g, "&lt;")
@ -98,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) => {
@ -123,8 +107,19 @@ export default function GenericModal({
function validatePrompt(closeModal: boolean): void {
//nodeClass is always null on tweaks
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) {
let inputVariables = apiReturn.data.input_variables ?? [];
if (inputVariables && inputVariables.length === 0) {
@ -132,6 +127,18 @@ export default function GenericModal({
setNoticeData({
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({
@ -144,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);
@ -153,18 +164,21 @@ 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 open={modalOpen} setOpen={setModalOpen}>
<BaseModal
onChangeOpenModal={(open) => {}}
open={modalOpen}
setOpen={setModalOpen}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header
description={(() => {
@ -214,7 +228,14 @@ export default function GenericModal({
}}
/>
) : type === TypeModal.PROMPT && (!isEdit || readonly) ? (
<TextAreaContentView />
<SanitizedHTMLWrapper
className={getClassByNumberLength()}
content={coloredContent}
onClick={() => {
setIsEdit(true);
}}
suppressWarning={true}
/>
) : type !== TypeModal.PROMPT ? (
<Textarea
//@ts-ignore
@ -254,7 +275,7 @@ export default function GenericModal({
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
key={index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
@ -293,9 +314,7 @@ export default function GenericModal({
setModalOpen(false);
break;
case TypeModal.PROMPT:
!inputValue || inputValue === ""
? setModalOpen(false)
: validatePrompt(false);
validatePrompt(false);
break;
default:

View file

@ -48,13 +48,15 @@ export default function LoginAdminPage() {
}
function getUser() {
if (getAuthentication) {
if (getAuthentication()) {
setTimeout(() => {
getLoggedUser()
.then((user) => {
setUserData(user);
})
.catch((error) => {});
.catch((error) => {
console.log("login admin page", error);
});
}, 1000);
}
}

View file

@ -1,10 +1,10 @@
import { cloneDeep } from "lodash";
import { X } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import LoadingComponent from "../../components/loadingComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input";
@ -16,8 +16,13 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
import {
ADMIN_HEADER_DESCRIPTION,
ADMIN_HEADER_TITLE,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { TabsContext } from "../../contexts/tabsContext";
import {
addUser,
deleteUser,
@ -33,12 +38,19 @@ export default function AdminPage() {
const [inputValue, setInputValue] = useState("");
const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(0);
const [index, setPageIndex] = useState(1);
const [loadingUsers, setLoadingUsers] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { userData } = useContext(AuthContext);
const [totalRowsCount, setTotalRowsCount] = useState(0);
const { setTabId } = useContext(TabsContext);
// set null id
useEffect(() => {
setTabId("");
}, []);
const userList = useRef([]);
useEffect(() => {
@ -51,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"];
@ -65,7 +77,9 @@ export default function AdminPage() {
function handleChangePagination(pageIndex: number, pageSize: number) {
setLoadingUsers(true);
getUsersPage(pageIndex, pageSize)
setPageSize(pageSize);
setPageIndex(pageIndex);
getUsersPage(pageSize * (pageIndex - 1), pageSize)
.then((users) => {
setTotalRowsCount(users["total_count"]);
userList.current = users["users"];
@ -78,7 +92,7 @@ export default function AdminPage() {
}
function resetFilter() {
setPageIndex(0);
setPageIndex(1);
setPageSize(10);
getUsers();
}
@ -168,270 +182,264 @@ export default function AdminPage() {
function handleNewUser(user: UserInputType) {
addUser(user)
.then((res) => {
resetFilter();
setSuccessData({
title: "Success! New user added!",
updateUser(res["id"], {
is_active: user.is_active,
is_superuser: user.is_superuser,
}).then((res) => {
resetFilter();
setSuccessData({
title: "Success! New user added!",
});
});
})
.catch((error) => {
setErrorData({
title: "Error on add new user",
list: [error["response"]["data"]["detail"]],
title: "Error when adding new user",
list: [error.response.data.detail],
});
});
}
return (
<>
<div className="flex flex-col">
<Header />
{userData && (
<div className="main-page-panel">
<div className="m-auto flex h-full flex-row justify-center">
<div className="basis-5/6">
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
Welcome back!
</h2>
<p className="text-muted-foreground">
Navigate through this section to efficiently oversee all
application users. From here, you can seamlessly manage
user accounts.
</p>
</div>
<div className="flex items-center space-x-2"></div>
</div>
{userList.current.length === 0 && !loadingUsers && (
<>
<div className="flex items-center justify-between">
<h2>There's no users registered :)</h2>
</div>
</>
)}
<>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
setInputValue("");
setFilterUserList(userList.current);
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
</div>
</div>
{loadingUsers && (
<div>
<strong>Loading...</strong>
</div>
)}
<div
className={
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
(loadingUsers ? " border-0" : "")
}
>
<Table className={"table-fixed bg-muted outline-1"}>
<TableHeader
className={
loadingUsers
? "hidden"
: "table-fixed bg-muted outline-1"
}
>
<TableRow>
<TableHead className="h-10">Id</TableHead>
<TableHead className="h-10">Username</TableHead>
<TableHead className="h-10">Active</TableHead>
<TableHead className="h-10">Superuser</TableHead>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody>
{filterUserList.map(
(user: UserInputType, index) => (
<TableRow key={index}>
<TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}>
<span className="cursor-default">
{user.id}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate py-2">
<ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip
content="Delete"
side="top"
>
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
)
)}
</TableBody>
)}
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={totalRowsCount}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
<Header />
{userData && (
<div className="admin-page-panel flex h-full flex-col pb-8">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Shield" className="w-6" />
{ADMIN_HEADER_TITLE}
</span>
</div>
<span className="admin-page-description-text">
{ADMIN_HEADER_DESCRIPTION}
</span>
<div className="flex w-full justify-between px-4">
<div className="flex w-96 items-center gap-4">
<Input
placeholder="Search Username"
value={inputValue}
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 ? (
<div
className="cursor-pointer"
onClick={() => {
setInputValue("");
setFilterUserList(userList.current);
}}
>
<IconComponent name="X" className="w-6 text-foreground" />
</div>
</div>
) : (
<div>
<IconComponent
name="Search"
className="w-6 text-foreground"
/>
</div>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
asChild
>
<Button variant="primary">New User</Button>
</UserManagementModal>
</div>
</div>
)}
</div>
{loadingUsers ? (
<div className="flex h-full w-full items-center justify-center">
<LoadingComponent remSize={12} />
</div>
) : userList.current.length === 0 ? (
<>
<div className="m-4 flex items-center justify-between text-sm">
No users registered.
</div>
</>
) : (
<>
<div
className={
"m-4 h-full overflow-x-hidden overflow-y-scroll rounded-md border-2 bg-background custom-scroll" +
(loadingUsers ? " border-0" : "")
}
>
<Table className={"table-fixed outline-1 "}>
<TableHeader
className={
loadingUsers ? "hidden" : "table-fixed bg-muted outline-1"
}
>
<TableRow>
<TableHead className="h-10">Id</TableHead>
<TableHead className="h-10">Username</TableHead>
<TableHead className="h-10">Active</TableHead>
<TableHead className="h-10">Superuser</TableHead>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody>
{filterUserList.map((user: UserInputType, index) => (
<TableRow key={index}>
<TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}>
<span className="cursor-default">{user.id}</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate py-2">
<ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
<ConfirmationModal
asChild
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<div className="flex w-fit">
<Checkbox
id="is_active"
checked={user.is_active}
/>
</div>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
<ConfirmationModal
asChild
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<div className="flex w-fit">
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</div>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={totalRowsCount}
paginate={(pageSize, pageIndex) => {
handleChangePagination(pageIndex, pageSize);
}}
></PaginatorComponent>
</>
)}
</div>
)}
</>
);
}

View file

@ -7,6 +7,7 @@ import { useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
export default function CommunityPage(): JSX.Element {
@ -74,7 +75,14 @@ export default function CommunityPage(): JSX.Element {
new and powerful features.
</span>
<div className="community-pages-flows-panel">
{!loadingExamples &&
{loadingExamples ? (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
</>
) : (
examples.map((flow, idx) => (
<CardComponent
key={idx}
@ -99,7 +107,8 @@ export default function CommunityPage(): JSX.Element {
</Button>
}
/>
))}
))
)}
</div>
</div>
</>

View file

@ -71,8 +71,15 @@ export default function Page({
setTabsState,
tabId,
} = useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext);
const {
types,
reactFlowInstance,
setReactFlowInstance,
templates,
setFilterEdge,
deleteNode,
deleteEdge,
} = useContext(typesContext);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { takeSnapshot } = useContext(undoRedoContext);
@ -114,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 });
@ -162,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);
@ -312,7 +350,14 @@ export default function Page({
setNodes((nds) => nds.concat(newNode));
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot();
uploadFlow(false, event.dataTransfer.files.item(0)!);
if (event.dataTransfer.files.item(0)!.type === "application/json") {
uploadFlow(false, event.dataTransfer.files.item(0)!);
} else {
setErrorData({
title: "Invalid file type",
list: ["Please upload a JSON file"],
});
}
}
},
// Specify dependencies for useCallback
@ -389,6 +434,10 @@ export default function Page({
[]
);
const onPaneClick = useCallback((flow) => {
setFilterEdge([]);
}, []);
return (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
@ -427,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}
@ -436,6 +486,7 @@ export default function Page({
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
>
<Background className="" />
{!view && (
@ -456,7 +507,10 @@ export default function Page({
reactFlowInstance!,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(newFlow,getNodeId);
const newGroupNode = generateNodeFromFlow(
newFlow,
getNodeId
);
setNodes((oldNodes) => [
...oldNodes.filter(
(oldNodes) =>

View file

@ -1,3 +1,4 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
@ -18,7 +19,8 @@ import { classNames } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
export default function ExtraSidebar(): JSX.Element {
const { data, templates } = useContext(typesContext);
const { data, templates, getFilterEdge, setFilterEdge } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
@ -42,6 +44,10 @@ export default function ExtraSidebar(): JSX.Element {
// Handle showing components after use search input
function handleSearchInput(e: string) {
if (e === "") {
setFilterData(data);
return;
}
setFilterData((_) => {
let ret = {};
Object.keys(data).forEach((d: keyof APIObjectType, i) => {
@ -69,6 +75,59 @@ export default function ExtraSidebar(): JSX.Element {
setErrorData({ title: " Components with errors: ", list: errors });
}, []);
function handleBlur() {
if (!search && search === "") {
setFilterData(data);
setFilterEdge([]);
setSearch("");
}
}
useEffect(() => {
if (getFilterEdge.length === 0 && search === "") {
setFilterData(data);
setFilterEdge([]);
setSearch("");
}
}, [getFilterEdge]);
useEffect(() => {
if (getFilterEdge?.length > 0) {
setFilterData((_) => {
let dataClone = cloneDeep(data);
let ret = {};
Object.keys(dataClone).forEach((d: keyof APIObjectType, i) => {
ret[d] = {};
if (getFilterEdge.some((x) => x.family === d)) {
ret[d] = dataClone[d];
const filtered = getFilterEdge
.filter((x) => x.family === d)
.pop()
.type.split(",");
for (let i = 0; i < filtered.length; i++) {
filtered[i] = filtered[i].trimStart();
}
if (filtered.some((x) => x !== "")) {
let keys = Object.keys(dataClone[d]).filter((nd) =>
filtered.includes(nd)
);
Object.keys(dataClone[d]).forEach((element) => {
if (!keys.includes(element)) {
delete ret[d][element];
}
});
}
}
});
setSearch("search");
return ret;
});
}
}, [getFilterEdge]);
return (
<div className="side-bar-arrangement">
<div className="side-bar-buttons-arrangement">
@ -99,16 +158,20 @@ export default function ExtraSidebar(): JSX.Element {
<ShadTooltip content={"Code"} side="top">
<div className="side-bar-button">
{flow && flow.data && (
<ApiModal flow={flow} disable={!isBuilt}>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="Code2"
className={
"side-bar-button-size" +
(isBuilt ? " " : " extra-side-bar-save-disable")
}
/>
</div>
<ApiModal flow={flow}>
<button
className={"w-full " + (!isBuilt ? "button-disable" : "")}
>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="Code2"
className={
"side-bar-button-size" +
(isBuilt ? " " : " extra-side-bar-save-disable")
}
/>
</div>
</button>
</ApiModal>
)}
</div>
@ -137,11 +200,12 @@ export default function ExtraSidebar(): JSX.Element {
<Separator />
<div className="side-bar-search-div-placement">
<Input
onFocusCapture={() => handleBlur()}
type="text"
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

View file

@ -1,7 +1,13 @@
import { useContext, useState } from "react";
import { useReactFlow } from "reactflow";
import { useReactFlow, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { TabsContext } from "../../../../contexts/tabsContext";
import EditNodeModal from "../../../../modals/EditNodeModal";
import { nodeToolbarPropsType } from "../../../../types/components";
@ -9,13 +15,16 @@ import {
expandGroupNode,
updateFlowPosition,
} from "../../../../utils/reactflowUtils";
import { classNames } from "../../../../utils/utils";
import { classNames, getRandomKeyByssmm } from "../../../../utils/utils";
export default function NodeToolbarComponent({
data,
setData,
deleteNode,
position,
setShowNode,
numberOfHandles,
showNode,
}: nodeToolbarPropsType): JSX.Element {
const [nodeLength, setNodeLength] = useState(
Object.keys(data.node!.template).filter(
@ -32,10 +41,43 @@ export default function NodeToolbarComponent({
data.node.template[templateField].type === "int")
).length
);
const updateNodeInternals = useUpdateNodeInternals();
function canMinimize() {
let countHandles: number = 0;
numberOfHandles.forEach((bool) => {
if (bool) countHandles += 1;
});
if (countHandles > 1) return false;
return true;
}
const isMinimal = canMinimize();
const isGroup = data.node?.flow ? true : false;
const { paste } = useContext(TabsContext);
const reactFlowInstance = useReactFlow();
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
const [selectedValue, setSelectedValue] = useState("");
const handleSelectChange = (event) => {
setSelectedValue(event);
if (event.includes("advanced")) {
return setShowModalAdvanced(true);
}
setShowModalAdvanced(false);
if (event.includes("show")) {
setShowNode((prev) => !prev);
updateNodeInternals(data.id);
}
if (event.includes("disabled")) {
return;
}
if (event.includes("ungroup")) {
updateFlowPosition(position, data.node?.flow!);
expandGroupNode(data, reactFlowInstance);
}
};
return (
<>
<div className="w-26 h-10">
@ -103,43 +145,105 @@ export default function NodeToolbarComponent({
</a>
</ShadTooltip>
<ShadTooltip content="Edit" side="top">
<div>
<EditNodeModal
data={data}
setData={setData}
nodeLength={nodeLength}
>
<div
{isMinimal || isGroup ? (
<Select onValueChange={handleSelectChange} value={selectedValue}>
<ShadTooltip content="More" side="top">
<SelectTrigger>
<div>
<div
className={classNames(
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
>
<IconComponent
name="MoreHorizontal"
className="relative left-2 h-4 w-4"
/>
</div>
</div>
</SelectTrigger>
</ShadTooltip>
<SelectContent>
<SelectItem
value={
getRandomKeyByssmm() +
(nodeLength == 0 ? "disabled" : "advanced")
}
>
<div
className={
"flex " +
(nodeLength == 0
? "text-muted-foreground"
: "text-primary")
}
>
<IconComponent
name="Settings2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Edit{" "}
</div>{" "}
</SelectItem>
{isMinimal && (
<SelectItem value={getRandomKeyByssmm() + "show"}>
<div className="flex">
<IconComponent
name={showNode ? "Minimize2" : "Maximize2"}
className="relative top-0.5 mr-2 h-4 w-4"
/>
{showNode ? "Minimize" : "Expand"}
</div>
</SelectItem>
)}
{isGroup && (
<SelectItem value={getRandomKeyByssmm() + "ungroup"}>
<div className="flex">
<IconComponent
name="Ungroup"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Ungroup{" "}
</div>
</SelectItem>
)}
</SelectContent>
</Select>
) : (
<ShadTooltip content="Edit" side="top">
<div>
<button
disabled={nodeLength === 0}
onClick={() => setShowModalAdvanced(true)}
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
"relative -ml-px inline-flex items-center rounded-r-md 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" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground") +
(isGroup ? "" : " rounded-r-md")
: " text-foreground")
)}
>
<IconComponent name="Settings2" className="h-4 w-4 " />
</div>
</EditNodeModal>
</div>
</ShadTooltip>
{isGroup && (
<ShadTooltip content="Ungroup" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center rounded-r-md bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)}
onClick={(event) => {
event.preventDefault();
updateFlowPosition(position, data.node?.flow!);
expandGroupNode(data, reactFlowInstance);
}}
>
<IconComponent name="Ungroup" className="h-4 w-4" />
</button>
</button>
</div>
</ShadTooltip>
)}
{showModalAdvanced && (
<EditNodeModal
data={data}
setData={setData}
nodeLength={nodeLength}
open={showModalAdvanced}
onClose={(modal) => {
setShowModalAdvanced(modal);
}}
>
<></>
</EditNodeModal>
)}
</span>
</div>
</>

View file

@ -1,4 +1,4 @@
import { useContext, useEffect } from "react";
import { useContext, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import { CardComponent } from "../../components/cardComponent";
@ -7,6 +7,7 @@ import Header from "../../components/headerComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage(): JSX.Element {
const {
@ -19,6 +20,7 @@ export default function HomePage(): JSX.Element {
uploadFlow,
isLoading,
} = useContext(TabsContext);
const { setErrorData } = useContext(alertContext);
const dropdownOptions = [
{
name: "Import from JSON",
@ -35,9 +37,40 @@ export default function HomePage(): JSX.Element {
}, []);
const navigate = useNavigate();
useEffect(() => {
console.log(isLoading);
}, [isLoading]);
const [isDragging, setIsDragging] = useState(false);
const dragOver = (e) => {
e.preventDefault();
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();
};
const dragLeave = () => {
setIsDragging(false);
};
const fileDrop = (e) => {
e.preventDefault();
setIsDragging(false);
if (e.dataTransfer.types.some((types) => types === "Files")) {
if (e.dataTransfer.files.item(0).type === "application/json") {
uploadFlow(true, e.dataTransfer.files.item(0)!);
} else {
setErrorData({
title: "Invalid file type",
list: ["Please upload a JSON file"],
});
}
}
};
// Personal flows display
return (
@ -82,40 +115,63 @@ export default function HomePage(): JSX.Element {
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{isLoading && flows.length == 0 ? (
<div
onDragOver={dragOver}
onDragEnter={dragEnter}
onDragLeave={dragLeave}
onDrop={fileDrop}
className={
"h-full w-full " +
(isDragging
? "mb-24 flex flex-col items-center justify-center gap-4 text-2xl font-light"
: "")
}
>
{isDragging ? (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
<IconComponent
name="ArrowUpToLine"
className="h-12 w-12 stroke-1"
/>
Drop your flow here
</>
) : (
flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))
<div className="main-page-flows-display">
{isLoading && flows.length == 0 ? (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
</>
) : (
flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))
)}
</div>
)}
</div>
</div>

View file

@ -0,0 +1,173 @@
import * as Form from "@radix-ui/react-form";
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import GradientChooserComponent from "../../components/gradientChooserComponent";
import Header from "../../components/headerComponent";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { TabsContext } from "../../contexts/tabsContext";
import { resetPassword, updateUser } from "../../controllers/API";
import {
inputHandlerEventType,
patchUserInputStateType,
} from "../../types/components";
import { gradients } from "../../utils/styleUtils";
export default function ProfileSettingsPage(): JSX.Element {
const { setTabId } = useContext(TabsContext);
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE
);
// set null id
useEffect(() => {
setTabId("");
}, []);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { userData, setUserData } = useContext(AuthContext);
const { password, cnfPassword, gradient } = inputState;
async function handlePatchUser() {
if (password !== cnfPassword) {
setErrorData({
title: "Error changing password",
list: ["Passwords do not match"],
});
return;
}
try {
if (password !== "") await resetPassword(userData!.id, { password });
if (gradient !== "")
await updateUser(userData!.id, { profile_image: gradient });
if (gradient !== "") {
let newUserData = cloneDeep(userData);
newUserData!.profile_image = gradient;
setUserData(newUserData);
}
handleInput({ target: { name: "password", value: "" } });
handleInput({ target: { name: "cnfPassword", value: "" } });
setSuccessData({ title: "Changes saved successfully!" });
} catch (error) {
setErrorData({
title: "Error saving changes",
list: [(error as any).response.data.detail],
});
}
}
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
return (
<>
<Header />
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="User" className="w-6" />
Profile Settings
</span>
</div>
<span className="community-page-description-text">
Change your profile settings like your password and your profile
picture.
</span>
<Form.Root
onSubmit={(event) => {
handlePatchUser();
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
className="flex h-full flex-col px-6 pb-16"
>
<div className="flex h-full flex-col gap-4">
<div className="flex gap-4">
<div className="mb-3 w-96">
<Form.Field name="password">
<Form.Label className="data-[invalid]:label-invalid">
Password{" "}
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your password
</Form.Message>
</Form.Field>
</div>
<div className="mb-3 w-96">
<Form.Field name="cnfPassword">
<Form.Label className="data-[invalid]:label-invalid">
Confirm Password{" "}
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "cnfPassword", value } });
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</div>
<Form.Field name="gradient">
<Form.Label className="data-[invalid]:label-invalid">
Profile Gradient{" "}
</Form.Label>
<div className="mt-4 w-[1010px]">
<GradientChooserComponent
value={
gradient == ""
? userData!.profile_image ??
gradients[
parseInt(userData!.id ?? "", 30) % gradients.length
]
: gradient
}
onChange={(value) => {
handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</Form.Field>
</div>
<div className="flex w-full justify-end">
<div className="w-32">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full" type="submit">
Save
</Button>
</Form.Submit>
</div>
</div>
</Form.Root>
</div>
</>
);
}

View file

@ -54,11 +54,13 @@ export default function LoginPage(): JSX.Element {
setTimeout(() => {
getLoggedUser()
.then((user) => {
const isSuperUser = user.is_superuser;
const isSuperUser = user!.is_superuser;
setIsAdmin(isSuperUser);
setUserData(user);
})
.catch((error) => {});
.catch((error) => {
console.log("login page", error);
});
}, 500);
}
}

View file

@ -1,5 +1,5 @@
import * as Form from "@radix-ui/react-form";
import { FormEvent, useContext, useState } from "react";
import { FormEvent, useContext, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
@ -20,6 +20,8 @@ export default function SignUp(): JSX.Element {
const [inputState, setInputState] =
useState<signUpInputStateType>(CONTROL_INPUT_STATE);
const [isDisabled, setDisableBtn] = useState<boolean>(true);
const { password, cnfPassword, username } = inputState;
const { setErrorData, setSuccessData } = useContext(alertContext);
const navigate = useNavigate();
@ -30,6 +32,13 @@ export default function SignUp(): JSX.Element {
setInputState((prev) => ({ ...prev, [name]: value }));
}
useEffect(() => {
if (password !== cnfPassword) return setDisableBtn(true);
if (password === "" || cnfPassword === "") return setDisableBtn(true);
if (username === "") return setDisableBtn(true);
setDisableBtn(false);
}, [password, cnfPassword, username, handleInput]);
function handleSignup(): void {
const { username, password } = inputState;
const newUser: UserInputType = {
@ -158,6 +167,7 @@ export default function SignUp(): JSX.Element {
<div className="w-full">
<Form.Submit asChild>
<Button
disabled={isDisabled}
type="submit"
className="mr-3 mt-6 w-full"
onClick={() => {

View file

@ -9,6 +9,7 @@ import ApiKeysPage from "./pages/ApiKeysPage";
import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage";
import ProfileSettingsPage from "./pages/ProfileSettingsPage";
import ViewPage from "./pages/ViewPage";
import DeleteAccountPage from "./pages/deleteAccountPage";
import LoginPage from "./pages/loginPage";
@ -95,6 +96,14 @@ const Router = () => {
/>
<Route path="/account">
<Route
path="settings"
element={
<ProtectedRoute>
<ProfileSettingsPage />
</ProtectedRoute>
}
/>
<Route
path="delete"
element={

View file

@ -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,13 +205,17 @@
}
.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 {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
}
.admin-page-panel {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
}
.main-page-nav-arrangement {
@apply flex-max-width justify-between px-6 py-12 pb-2;
}
@ -228,6 +232,10 @@
@apply flex w-[60%] px-6 pb-14 text-muted-foreground;
}
.admin-page-description-text {
@apply flex w-[80%] px-6 pb-8 text-muted-foreground;
}
.main-page-flows-display {
@apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
}
@ -256,10 +264,10 @@
@apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
}
.generic-node-div {
@apply relative flex w-96 flex-col justify-center rounded-lg bg-background;
@apply relative flex flex-col justify-center bg-background;
}
.generic-node-div-title {
@apply flex w-full items-center justify-between gap-8 rounded-t-lg border-b bg-muted p-4;
@apply flex w-full items-center gap-8 border-b bg-muted p-4;
}
.generic-node-title-arrangement {
@apply flex-max-width items-center truncate;
@ -303,7 +311,7 @@
@apply hover:text-accent-foreground hover:transition-all;
}
.generic-node-desc {
@apply h-full w-full py-5 text-foreground;
@apply h-full w-full text-foreground;
}
.generic-node-desc-text {
@apply w-full px-5 pb-3 text-sm text-muted-foreground;
@ -342,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 {
@ -352,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;
@ -512,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;
@ -845,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 flex-col max-w-full 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;
@ -874,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;
@ -1042,11 +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:border-destructive focus:ring-destructive;
}
}

View file

@ -29,7 +29,6 @@ pre {
animation: slideUp 300ms ease-out;
}
.gradient-end {
animation: gradient-motion-end 3s infinite forwards;
}
@ -50,4 +49,22 @@ select:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0px 1000px #fff6d0 inset;
box-shadow: 0 0 0px 1000px #fff6d0 inset;
color: black;
}
}
.ace_scrollbar::-webkit-scrollbar {
height: 8px;
width: 8px;
}
.ace_scrollbar::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
.ace_scrollbar::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 999px;
}
.ace_scrollbar::-webkit-scrollbar-thumb:hover {
background-color: #bbb;
border-radius: 999px;
}

View file

@ -7,6 +7,11 @@ export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
export type APITemplateType = {
[key: string]: TemplateVariableType;
};
export type CustomFieldsType = {
[key: string]: Array<string>;
};
export type APIClassType = {
base_classes: Array<string>;
description: string;
@ -14,6 +19,7 @@ export type APIClassType = {
display_name: string;
input_types?: Array<string>;
output_types?: Array<string>;
custom_fields?: CustomFieldsType;
beta?: boolean;
documentation: string;
error?: string;
@ -24,6 +30,8 @@ export type APIClassType = {
| APITemplateType
| boolean
| FlowType
| CustomFieldsType
| boolean
| undefined;
};
@ -48,7 +56,7 @@ export type sendAllProps = {
description: string;
viewport: Viewport;
inputs: { text?: string };
chatKey: string;
chatHistory: { message: string | object; isSend: boolean }[];
};
export type errorsTypeAPI = {
@ -88,11 +96,25 @@ export type LoginAuthType = {
token_type?: string;
};
export type changeUser = {
username?: string;
is_active?: boolean;
is_superuser?: boolean;
password?: string;
profile_image?: string;
};
export type resetPasswordType = {
password?: string;
profile_image?: string;
};
export type Users = {
id: string;
username: string;
is_active: boolean;
is_superuser: boolean;
profile_image: string;
create_at: Date;
updated_at: Date;
};

View file

@ -8,5 +8,6 @@ export type ChatMessageType = {
isSend: boolean;
thought?: string;
files?: Array<{ data: string; type: string; data_type: string }>;
prompt?: string;
chatKey: string;
};

View file

@ -50,6 +50,7 @@ export type ParameterComponentType = {
optionalHandle?: Array<String> | null;
info?: string;
proxy?: { field: string; id: string };
showNode?: boolean;
};
export type InputListComponentType = {
value: string[];
@ -58,6 +59,23 @@ export type InputListComponentType = {
editNode?: boolean;
};
export type KeyPairListComponentType = {
value: any;
onChange: (value: Object[]) => void;
disabled: boolean;
editNode?: boolean;
duplicateKey?: boolean;
advanced?: boolean | null;
dataValue?: any;
};
export type DictComponentType = {
value: any;
onChange: (value) => void;
disabled: boolean;
editNode?: boolean;
};
export type TextAreaComponentType = {
field_name?: string;
nodeClass?: APIClassType;
@ -69,6 +87,17 @@ export type TextAreaComponentType = {
readonly?: boolean;
};
export type PromptAreaComponentType = {
field_name?: string;
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType) => void;
disabled: boolean;
onChange: (value: string[] | string) => void;
value: string;
readonly?: boolean;
editNode?: boolean;
};
export type CodeAreaComponentType = {
disabled: boolean;
onChange: (value: string[] | string) => void;
@ -179,6 +208,7 @@ export type IconComponentProps = {
name: string;
className?: string;
iconColor?: string;
onClick?: () => void;
};
export type InputProps = {
@ -239,6 +269,7 @@ export type PaginatorComponentType = {
export type ConfirmationModalType = {
title: string;
titleHeader: string;
asChild?: boolean;
modalContent: string;
modalContentTitle: string;
cancelText: string;
@ -259,6 +290,7 @@ export type UserManagementType = {
icon: string;
data?: any;
index?: number;
asChild?: boolean;
onConfirm: (index, data) => void;
};
@ -267,6 +299,12 @@ export type loginInputStateType = {
password: string;
};
export type patchUserInputStateType = {
password: string;
cnfPassword: string;
gradient: string;
};
export type UserInputType = {
username: string;
password: string;
@ -396,6 +434,9 @@ export type nodeToolbarPropsType = {
deleteNode: (idx: string) => void;
setData: (newState: NodeDataType) => void;
position: XYPosition;
setShowNode: (boolean: any) => void;
numberOfHandles: boolean[] | [];
showNode: boolean;
};
export type parsedDataType = {
@ -520,7 +561,7 @@ export type codeTabsPropsType = {
) => string;
buildTweakObject?: (
tw: string,
changes: string | string[] | boolean | number,
changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType
) => string | void;
};

View file

@ -2,8 +2,7 @@ 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;

View file

@ -9,7 +9,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;
@ -18,6 +18,9 @@ export type typesContextType = {
setData: (newState: {}) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
deleteEdge: (idx: string | Array<string>) => void;
};
export type alertContextType = {

View file

@ -1,4 +1,4 @@
import _, { get } from "lodash";
import _ from "lodash";
import {
Connection,
Edge,
@ -25,7 +25,7 @@ import {
unselectAllNodesType,
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { toNormalCase, toTitleCase, truncateDisplayName } from "./utils";
import { toNormalCase, toTitleCase } from "./utils";
const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges({
@ -54,8 +54,8 @@ export function cleanEdges({
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 (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);
@ -213,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!",
@ -228,43 +225,58 @@ export function validateNode(
node: { template },
} = node.data;
return Object.keys(template).reduce(
(errors: Array<string>, t) =>
errors.concat(
template[t].required &&
template[t].show &&
(template[t].value === undefined ||
template[t].value === null ||
template[t].value === "") &&
!reactFlowInstance
.getEdges()
.some(
(edge) =>
(scapeJSONParse(edge.targetHandle!) as targetHandleType)
.fieldName === t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id
)
? [
`${type} is missing ${
template.display_name || toNormalCase(template[t].name)
}.`,
]
: []
),
[] as string[]
);
return Object.keys(template).reduce((errors: Array<string>, t) => {
if (
template[t].required &&
template[t].show &&
(template[t].value === undefined ||
template[t].value === null ||
template[t].value === "") &&
!edges.some(
(edge) =>
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id
)
) {
errors.push(
`${type} is missing ${
template.display_name || toNormalCase(template[t].name)
}.`
);
} else if (
template[t].type === "dict" &&
template[t].required &&
template[t].show &&
(template[t].value !== undefined ||
template[t].value !== null ||
template[t].value !== "")
) {
if (hasDuplicateKeys(template[t].value))
errors.push(
`${type} (${
template.display_name || template[t].name
}) contains duplicate keys with the same values.`
);
if (hasEmptyKey(template[t].value))
errors.push(
`${type} (${
template.display_name || template[t].name
}) field must not be empty.`
);
}
return errors;
}, [] 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[]) {
@ -324,7 +336,9 @@ export function updateEdgesHandleIds({
}
export function handleKeyDown(
e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>,
e:
| React.KeyboardEvent<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>,
inputValue: string | string[] | null,
block: string
) {
@ -359,6 +373,80 @@ export function getConnectedNodes(
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
}
export function convertObjToArray(singleObject: object | string) {
if (typeof singleObject === "string") {
singleObject = JSON.parse(singleObject);
}
if (Array.isArray(singleObject)) return singleObject;
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) {
for (const key in obj) {
if (keys[key]) {
return true;
}
keys[key] = true;
}
}
return false;
}
export function hasEmptyKey(objArray) {
for (const obj of objArray) {
for (const key in obj) {
if (obj.hasOwnProperty(key) && key === "") {
return true; // Found an empty key
}
}
}
return false; // No empty keys found
}
export function convertValuesToNumbers(arr) {
return arr.map((obj) => {
const newObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
let value = obj[key];
if (/\s/g.test(value)) {
value = value.trim();
}
newObj[key] =
value === "" || isNaN(value) ? value.toString() : Number(value);
}
}
return newObj;
});
}
export function scapedJSONStringfy(json: object): string {
return customStringify(json).replace(/"/g, "œ");
}
@ -502,15 +590,15 @@ export function validateSelection(
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;
// 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
@ -591,11 +679,11 @@ export function mergeNodeTemplates({
} 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].display_name
: nodeTemplate[key].name
? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key))
: toTitleCase(key);
}
}
});
@ -659,15 +747,18 @@ export function generateNodeTemplate(Flow: FlowType) {
return template;
}
export function generateNodeFromFlow(flow: FlowType,getNodeId:(type:string)=>string): NodeType {
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 id = getNodeId(outputNode?.data.type!);
const newGroupNode: NodeType = {
data: {
id,
id,
type: outputNode?.data.type!,
node: {
output_types: outputNode!.data.node!.output_types,
@ -749,7 +840,7 @@ export function ungroupNode(
fieldName: field,
type,
id: proxyId,
inputTypes: inputTypes
inputTypes: inputTypes,
};
if (node.data.node?.flow) {
newTargetHandle.proxy = {
@ -816,10 +907,10 @@ export function expandGroupNode(
groupNode: NodeDataType,
ReactFlowInstance: ReactFlowInstance
) {
const { template,flow } = _.cloneDeep(groupNode.node!);
const { template, flow } = _.cloneDeep(groupNode.node!);
const gNodes: NodeType[] = flow?.data?.nodes!;
const gEdges = flow!.data!.edges;
console.log(gEdges)
console.log(gEdges);
//redirect edges to correct proxy node
let updatedEdges: Edge[] = [];
ReactFlowInstance.getEdges().forEach((edge) => {
@ -838,7 +929,7 @@ export function expandGroupNode(
fieldName: field,
type,
id: proxyId,
inputTypes: inputTypes
inputTypes: inputTypes,
};
if (node.data.node?.flow) {
newTargetHandle.proxy = {
@ -880,7 +971,7 @@ export function expandGroupNode(
} else {
display_name = gNodes[nodeIndex].data.node!.template[field].name;
}
if(gNodes[nodeIndex].data.node!.template[field].proxy){
if (gNodes[nodeIndex].data.node!.template[field].proxy) {
proxy = gNodes[nodeIndex].data.node!.template[field].proxy;
}
gNodes[nodeIndex].data.node!.template[field] = template[key];
@ -888,11 +979,10 @@ export function expandGroupNode(
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
gNodes[nodeIndex].selected = false;
if(proxy){
if (proxy) {
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
}
else{
delete gNodes[nodeIndex].data.node!.template[field].proxy
} else {
delete gNodes[nodeIndex].data.node!.template[field].proxy;
}
}
});
@ -908,7 +998,7 @@ export function expandGroupNode(
...gEdges,
...updatedEdges,
];
console.log(edges)
console.log(edges);
ReactFlowInstance.setNodes(nodes);
ReactFlowInstance.setEdges(edges);
}
@ -931,14 +1021,14 @@ export function getGroupStatus(
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) => console.log(ssData[id]));
ids.forEach((id) => {
if (!ssData[id]) {
status = ssData[id]
return
status = ssData[id];
return;
}
if (!ssData[id].valid) {
status = { valid: false, params: ssData[id].params };
status = { valid: false, params: ssData[id].params };
}
});
return status;

View file

@ -1,5 +1,7 @@
import {
ArrowUpToLine,
Bell,
BookMarked,
Check,
CheckCircle2,
ChevronDown,
@ -44,10 +46,13 @@ import {
Link,
Lock,
LucideSend,
Maximize2,
Menu,
MessageCircle,
MessageSquare,
MessagesSquare,
Minimize2,
Minus,
MoonIcon,
MoreHorizontal,
Paperclip,
@ -59,7 +64,9 @@ import {
Scissors,
Search,
Settings2,
Shield,
Sparkles,
Square,
SunIcon,
TerminalSquare,
Trash2,
@ -67,6 +74,7 @@ import {
Ungroup,
Unplug,
Upload,
User,
UserCog2,
UserMinus2,
UserPlus2,
@ -88,7 +96,7 @@ import { EvernoteIcon } from "../icons/Evernote";
import { FBIcon } from "../icons/FacebookMessenger";
import { GitBookIcon } from "../icons/GitBook";
import { GoogleIcon } from "../icons/Google";
import GradientSparkles from "../icons/GradientSparkles";
import { GradientSparkles } from "../icons/GradientSparkles";
import { HuggingFaceIcon } from "../icons/HuggingFace";
import { IFixIcon } from "../icons/IFixIt";
import { MetaIcon } from "../icons/Meta";
@ -188,6 +196,7 @@ export const nodeNames: { [char: string]: string } = {
};
export const nodeIconsLucide: iconsType = {
ArrowUpToLine: ArrowUpToLine,
Chroma: ChromaIcon,
AirbyteJSONLoader: AirbyteIcon,
Anthropic: AnthropicIcon,
@ -222,6 +231,7 @@ export const nodeIconsLucide: iconsType = {
ChatVertexAI: VertexAIIcon,
VertexAIEmbeddings: VertexAIIcon,
agents: Rocket,
User,
WikipediaAPIWrapper: SvgWikipedia,
chains: Link,
memories: Cpu,
@ -262,6 +272,7 @@ export const nodeIconsLucide: iconsType = {
Bell,
ChevronLeft,
ChevronDown,
Shield,
Plus,
Redo,
Settings2,
@ -306,4 +317,9 @@ export const nodeIconsLucide: iconsType = {
Group,
ChevronUp,
Ungroup,
BookMarked,
Minus,
Square,
Minimize2,
Maximize2,
};

View file

@ -299,6 +299,7 @@ export function getChatInputField(flow: FlowType, tabsState?: TabsState) {
*/
export function getPythonApiCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
tabsState?: TabsState
): string {
@ -325,7 +326,9 @@ TWEAKS = ${
: JSON.stringify(tweaks, null, 2)
}
def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None) -> dict:
def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
!isAuth ? `, api_key: Optional[str] = None` : ""
}) -> dict:
"""
Run a flow with a given message and optional tweaks.
@ -337,16 +340,20 @@ def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None) -> dict:
api_url = f"{BASE_API_URL}/{flow_id}"
payload = {"inputs": inputs}
headers = None
if tweaks:
payload["tweaks"] = tweaks
response = requests.post(api_url, json=payload)
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}
print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS))`;
${!isAuth ? `api_key = "<your api key>"` : ""}
print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS${
!isAuth ? `, api_key=api_key` : ""
}))`;
}
/**
@ -356,6 +363,7 @@ print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS))`;
*/
export function getCurlCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
tabsState?: TabsState
): string {
@ -367,7 +375,9 @@ export function getCurlCode(
${window.location.protocol}//${
window.location.host
}/api/v1/process/${flowId} \\
-H 'Content-Type: application/json' \\
-H 'Content-Type: application/json'\\${
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
}
-d '{"inputs": ${inputs}, "tweaks": ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
@ -405,7 +415,11 @@ flow(inputs)`;
* @param {string} flow - The current flow.
* @returns {string} - The widget code
*/
export function getWidgetCode(flow: FlowType, tabsState?: TabsState): string {
export function getWidgetCode(
flow: FlowType,
isAuth: boolean,
tabsState?: TabsState
): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs(tabsState!, flow.id);
@ -425,7 +439,13 @@ chat_input_field: Input key that you want the chat to send the user message with
chat_input_field="${chat_input_field}"
`
: ""
}host_url="http://localhost:7860"
}host_url="http://localhost:7860"${
!isAuth
? `
api_key="..."`
: ""
}
></langflow-chat>`;
}

View 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;'

View 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"`;

View 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();
});
});

View file

@ -0,0 +1,32 @@
import { expect, test } from "@playwright/test";
test.describe("Auto_login tests", () => {
test("auto_login sign in", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.goto("http:localhost:3000/");
await page.getByRole("button", { name: "Community Examples" }).click();
await page.waitForSelector(".community-pages-flows-panel");
expect(
await page
.locator(".community-pages-flows-panel")
.evaluate((el) => el.children)
).toBeTruthy();
});
test("auto_login block_admin", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.goto("http:localhost:3000/");
await page.getByRole("button", { name: "Community Examples" }).click();
await page.goto("http:localhost:3000/login");
await page.getByRole("button", { name: "Community Examples" }).click();
await page.goto("http:localhost:3000/admin");
await page.getByRole("button", { name: "Community Examples" }).click();
await page.goto("http:localhost:3000/admin/login");
await page.getByRole("button", { name: "Community Examples" }).click();
});
});

View file

@ -0,0 +1,128 @@
import { expect, test } from "@playwright/test";
test.describe("Login Tests", () => {
test("Login_Success", async ({ page }) => {
await page.route("**/api/v1/login", async (route) => {
const json = {
access_token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWNlM2FkOS1iZTE2LTRiNjgtOGRhYi1hYjA4YTVjMmZjZTkiLCJleHAiOjE2OTUyNTIwNTh9.MBYFwMhTcZnsW_L7p4qavUhSDylCllJQWUCJdU1wX8o",
refresh_token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWNlM2FkOS1iZTE2LTRiNjgtOGRhYi1hYjA4YTVjMmZjZTkiLCJ0eXBlIjoicmYiLCJleHAiOjE2OTUyNTI2NTh9.a4wL9-XK_zyTyrXduBFgCsODFXrqiByVr5HOeiCbiQA",
token_type: "bearer",
};
await route.fulfill({ json });
});
await page.goto("http://localhost:3000/");
await page.waitForURL("http://localhost:3000/login");
await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
await page.getByPlaceholder("Username").click();
await page.getByPlaceholder("Username").fill("test");
await page.getByPlaceholder("Password").click();
await page.getByPlaceholder("Password").fill("test");
await page.getByRole("button", { name: "Sign in" }).click();
await page.getByRole("button", { name: "Community Examples" }).click();
await page.waitForSelector(".community-pages-flows-panel");
expect(
await page
.locator(".community-pages-flows-panel")
.evaluate((el) => el.children)
).toBeTruthy();
});
test("Login Error", async ({ page }) => {
await page.route("**/api/v1/login", async (route) => {
const json = { detail: "Incorrect username or password" };
await route.fulfill({ json, status: 401 });
});
await page.goto("http://localhost:3000/");
await page.waitForURL("http://localhost:3000/login");
await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
await page.getByPlaceholder("Username").click();
await page.getByPlaceholder("Username").fill("test");
await page.getByPlaceholder("Password").click();
await page.getByPlaceholder("Password").fill("test5");
await page.getByRole("button", { name: "Sign in" }).click();
await page.getByRole("heading", { name: "Error signing in" }).click();
});
test("Login create account wrong form", async ({ page }) => {
const fullfillForm = async (username, password, confirmPassword) => {
await page.getByPlaceholder("Username").click();
await page.getByPlaceholder("Username").fill(username);
await page.getByPlaceholder("Password", { exact: true }).click();
await page.getByPlaceholder("Password", { exact: true }).fill(password);
await page.getByPlaceholder("Confirm your password").click();
await page
.getByPlaceholder("Confirm your password")
.fill(confirmPassword);
};
await page.goto("http://localhost:3000/");
await page.waitForURL("http://localhost:3000/login");
await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
await page
.getByRole("button", { name: "Don't have an account? Sign Up" })
.click();
await page.getByText("Sign up to Langflow").click();
await page.goto("http://localhost:3000/signup");
await page.getByText("Sign up to Langflow").click();
await fullfillForm("name", "vazz", "vazz5");
expect(
await page.getByRole("button", { name: "Sign up" }).isDisabled()
).toBeTruthy();
await fullfillForm("", "vazz", "vazz");
expect(
await page.getByRole("button", { name: "Sign up" }).isDisabled()
).toBeTruthy();
await fullfillForm("name", "", "");
expect(
await page.getByRole("button", { name: "Sign up" }).isDisabled()
).toBeTruthy();
await fullfillForm("", "", "");
expect(
await page.getByRole("button", { name: "Sign up" }).isDisabled()
).toBeTruthy();
});
test("Login create account success", async ({ page }) => {
await page.route("**/api/v1/users/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
username: "teste",
profile_image: null,
is_active: false,
is_superuser: false,
create_at: "2023-09-21T01:45:51.873303",
updated_at: "2023-09-21T01:45:51.873305",
last_login_at: null,
};
await route.fulfill({ json, status: 201 });
});
const submitForm = async (username, password, confirmPassword) => {
await page.getByPlaceholder("Username").click();
await page.getByPlaceholder("Username").fill(username);
await page.getByPlaceholder("Password", { exact: true }).click();
await page.getByPlaceholder("Password", { exact: true }).fill(password);
await page.getByPlaceholder("Confirm your password").click();
await page
.getByPlaceholder("Confirm your password")
.fill(confirmPassword);
};
await page.goto("http://localhost:3000/");
await page.waitForURL("http://localhost:3000/login");
await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
await page
.getByRole("button", { name: "Don't have an account? Sign Up" })
.click();
await page.getByText("Sign up to Langflow").click();
await page.goto("http://localhost:3000/signup");
await page.getByText("Sign up to Langflow").click();
await submitForm("teste", "pass", "pass");
await page.getByRole("button", { name: "Sign up" }).click();
await page.waitForURL("http://localhost:3000/login", { timeout: 1000 });
await page.getByText("Account created! Await admin activation.").click();
});
});

View file

@ -0,0 +1,5 @@
import { test } from "@playwright/test";
test("test", async ({ page }) => {
// Recording...
});