[
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.0/schema.json\",\n  \"changelog\": [\"@changesets/cli/changelog\", { \"repo\": \"FlowTestAI/FlowTest\" }],\n  \"commit\": false,\n  \"fixed\": [],\n  \"linked\": [],\n  \"access\": \"public\",\n  \"baseBranch\": \"main\",\n  \"updateInternalDependencies\": \"patch\",\n  \"ignore\": [],\n  \"changedFilePatterns\": [\"src/**/\"]\n}\n"
  },
  {
    "path": ".changeset/early-colts-approve.md",
    "content": "---\n'flowtestai-app': minor\n---\n\nAdd support for google geminin function calling in generating flow\n"
  },
  {
    "path": ".changeset/honest-bags-fail.md",
    "content": "---\n'flowtestai-app': minor\n---\n\nadd support for bearer token auth type\n"
  },
  {
    "path": ".changeset/proud-ants-flash.md",
    "content": "---\n'flowtestai-app': minor\n---\n\nallow request headers to be input by users\n"
  },
  {
    "path": ".eslintignore",
    "content": "# dependencies\n**/node_modules\n\n# production\n/build\n\n# intermal dependencies\n/server\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    browser: true,\n    es2021: true,\n    jest: true,\n  },\n  extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],\n  overrides: [\n    {\n      env: {\n        node: true,\n      },\n      files: ['.eslintrc.{js,cjs}'],\n      parserOptions: {\n        sourceType: 'script',\n      },\n    },\n  ],\n  parserOptions: {\n    ecmaVersion: 'latest',\n    sourceType: 'module',\n  },\n  plugins: ['react', 'react-hooks', 'prettier'],\n  rules: {\n    'react/prop-types': 'off', // keeping off for first phase\n    'no-unused-vars': 'off', // Getting a lot of Error for: '_' is assigned a value but never used  no-unused-vars. For now disabling this because  need to understand more about the use '_'.\n  },\n  settings: {\n    'import/resolver': {\n      node: {\n        moduleDirectory: ['node_modules', './src/*'],\n        extensions: ['.js', '.jsx', '.ts', '.tsx'],\n      },\n    },\n  },\n};\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Lines starting with '#' are comments.\n# Each line is a file pattern followed by one or more owners.\n\n# More details are here: https://help.github.com/articles/about-codeowners/\n\n# The '*' pattern is global owners.\n\n# Order is important. The last matching pattern has the most precedence.\n# The folders are ordered as follows:\n\n# In each subsection folders are ordered first by depth, then alphabetically.\n# This should make it easy to add new rules without breaking existing ones.\n\n# Global rule:\n*           @jsajal"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n**/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n## build\n**/dist\n**/build\n\n# db\n.flowtest\n\n# temp upload directory\nuploads/\n\n# env\n.env\n.env.example\n\n# Ide\n.vscode/"
  },
  {
    "path": ".npmrc",
    "content": "node-linker=hoisted #pnpm requirement when working with electron\nignore-workspace-root-check=true"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 120,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"jsxSingleQuote\": true,\n  \"bracketSpacing\": true,\n  \"plugins\": [\"prettier-plugin-tailwindcss\"]\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Guidelines\n\nWhen contributing to `FlowTestAI`, whether on GitHub or in other community spaces:\n\n- Be respectful, civil, and open-minded.\n- Before opening a new pull request, try searching through the [issue tracker](https://github.com/FlowTestAI/FlowTest/issues) for known issues or fixes.\n- If you want to make code changes based on your personal opinion(s), make sure you open an issue first describing the changes you want to make, and open a pull request only when your suggestions get approved by maintainers.\n\n## How to Contribute\n\n### Prerequisites\n\nIn order to not waste your time implementing a change that has already been declined, or is generally not needed, start by [opening an issue](https://github.com/FlowTestAI/FlowTest/issues/new/choose) describing the problem you would like to solve.\n\n### Setup your environment locally\n\n_Some commands will assume you have the GitHub CLI installed, if you haven't, consider [installing it](https://github.com/cli/cli#installation), but you can always use the Web UI if you prefer that instead._\n\nIn order to contribute to this project, you will need to fork the repository:\n\n```bash\ngh repo fork FlowTestAI/FlowTest\n```\n\nthen, clone it to your local machine:\n\n```bash\ngh repo clone <your-github-name>/FlowTest\n```\n\nThis project uses [pnpm](https://pnpm.io) as its package manager. Install it if you haven't already:\n\n```bash\nnpm install -g pnpm@9.0.6\n```\n\nThen, install the project's dependencies:\n\n```bash\npnpm install\n```\n\n### Implement your changes\n\nThis project is a monorepo. FlowTestAI is offered as a local electron desktop app. In lieu of that it has two major components, the main logical part of the application resides in `packages/flowtest-electron` and the renderer (UI) part of the application resides in `src`. We are also actively developing the CLI and it resides in `packages/flowtest-cli` directory.\n\nHere are some useful scripts for when you are developing:\n\n| Command         | Description                                        |\n| --------------- | -------------------------------------------------- |\n| `pnpm start`    | Builds and starts the FlowTest App on your desktop |\n| `pnpm build`    | Builds the application for development use         |\n| `pnpm format`   | Formats the code                                   |\n| `pnpm lint`     | Lints the code                                     |\n| `pnpm lint:fix` | Lints the code and fixes any errors                |\n| `pnpm clean`    | Deletes all node_modules in the project            |\n\n### When you're done\n\nPlease make a manual, functional test of your changes.\n\nCheck for formatting and linting errors:\n\n```bash\npnpm lint && pnpmt format\n```\n\nWhen making commits, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines, i.e. prepending the message with `feat:`, `fix:`, `chore:`, `docs:`, etc... You can use `git status` to double check which files have not yet been staged for commit:\n\n```bash\ngit add <file> && git commit -m \"feat/fix/chore/docs: commit message\"\n```\n\nIf your change should appear in the changelog, i.e. it changes some behavior of either the CLI or the outputted elctron application, it must be captured by `changeset`. If this does not apply to your contribution skip to the pr creation step.\n\nRun the changeset:\n\n```bash\npnpm changeset\n```\n\nand filling out the form with the appropriate information. Then, add the generated changeset to git:\n\n```bash\ngit add .changeset/*.md && git commit -m \"chore: add changeset\"\n```\n\nWhen all that's done, it's time to file a pull request to upstream:\n\n```bash\ngh pr create --web\n```\n\nand fill out the title and body appropriately. Again, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines for your title.\n\n## Credits\n\nThis documented was inspired by the contributing guidelines for [t3-oss/create-t3-app](https://github.com/t3-oss/create-t3-app/blob/main/CONTRIBUTING.md).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Sajal Jain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# FlowTestAI: Streamlining End-to-End API Testing\n\n[![Release Notes](https://img.shields.io/github/release/FlowTestAI/FlowTest)](https://github.com/FlowTestAI/FlowTest/releases)\n[![Linkedin](https://img.shields.io/badge/LinkedIn-blue?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/flowtestai)\n[![Twitter Follow](https://img.shields.io/twitter/follow/FlowTestAI?style=social)](https://twitter.com/FlowTestAI)\n[![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://discord.gg/Pf9tdSjPeF)\n\n💡 We are proud to announce that we were recently featured in a [LangChain](https://blog.langchain.dev/empowering-development-with-flowtestai/) blog post.\n\nFlowTestAI is a powerful, code-agnostic tool designed to simplify the creation and execution of end-to-end API tests. With its intuitive interface and robust features, FlowTestAI empowers developers and QA teams to streamline their API testing process, improve collaboration, and gain valuable insights into their API performance.\n\n<img width=\"1728\" alt=\"Screenshot 2024-04-18 at 5 41 43 PM\" src=\"https://github.com/FlowTestAI/FlowTest/assets/5829490/c04f6e3e-fe69-4d25-a008-ba558c8fe149\">\n\n## 🚀 Key Features\n\n- **Low Code/No Code Solution**: Create complex end-to-end API tests without writing code.\n- **Natural Language Processing**: Describe your test scenarios in plain English.\n- **Support Leading LLMs**: Choose from a wide range of leading LLMs: OpenAI, AWS Bedrock, Google Gemini etc.\n- **Drag-and-Drop Interface**: Visually design your API tests with ease.\n- **OpenAPI Spec Integration**: Automatically parse and pre-fill request nodes from your OpenAPI specifications.\n- **Cross-Platform Compatibility**: Available as an Desktop application for Mac, Windows, and Linux.\n- **Local File System Integration**: Direct interaction with local file system for enhanced privacy and control.\n- **Version Control Ready**: Easily collaborate using Git or any other VCS.\n- **CI/CD Ready**: Run tests in CI pipelines with our CLI tool.\n- **Advanced Analytics**: Gain insights into API performance and test results.\n\n## 🛠️ Getting Started\n\n### Desktop App Installation\n\n1. Download FlowTestAI for your OS from our [releases page](https://github.com/FlowTestAI/FlowTest/releases).\n2. Install and launch FlowTestAI like any other desktop application.\n3. Start creating end-to-end API tests using natural language or drag-and-drop.\n4. Save your work locally and use Git for version control, just like with traditional IDEs.\n\n### CLI Installation (for CI/CD)\n\n```bash\nnpm install -g flowtestai\n```\n\nhttps://www.npmjs.com/package/flowtestai\n\nThe CLI allows you to run flows created using FlowTestAI from command line interface making it easier to automate and run them in a CI/CD (continuous integration/development) fashion.\n\n[README](https://github.com/FlowTestAI/FlowTest/blob/main/packages/flowtest-cli/README.md)\n\n### Analytics Setup (Optional)\n\n1. Visit https://www.useflowtest.ai/\n2. Go to Products -> Analytics -> Get Access Key Pairs\n3. For CLI: Export key pairs as environment variables\n4. For IDE: Open Settings and paste the access key pairs\n5. Now start publishing scans for each test run.\n\n## 📚 Documentation\n\nhttps://flowtestai.gitbook.io/flowtestai\n\n## Setup\n\n## 💻 Production\n\nFlowTestAI is an electron app that runs entirely in your local environment interacting with your local file system just like other IDE(s) out there like VSCode, Intellij etc. The platform-specific binaries are available for download from our GitHub releases. We currently offer [binaries for macOS](https://github.com/FlowTestAI/FlowTest/releases), with versions for Windows and Linux under development 🚧. If you require a binary for a specific platform, please let us know in the Discussions section. We will prioritize your request accordingly.\n\n## 🔧 Development\n\n### Prerequisite\n\nThis package uses version >= 18 of Node.js. There are different ways that you can install Node.js, following are steps for [Node Verson Manager or NVM](https://github.com/nvm-sh/nvm). If you need steps for other methods than NVM then please check [Official Node.js documentation](https://nodejs.org/en/download/package-manager). Here is a sample walkthrough installing version 18.\n\n1. Installs nvm (Node Version Manager)\n\n   ```bash\n   curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash\n   ```\n\n2. Download and install Node.js\n\n   ```bash\n   nvm install 18\n   ```\n\n3. Verifies the right Node.js version is in the environment\n\n   ```bash\n   node -v # should print `v18.20.2`\n   ```\n\n4. Verifies the right NPM version is in the environment\n\n   ```bash\n   npm -v # should print `10.5.0`\n   ```\n\n### Main setup\n\n1. Clone the repository\n\n   ```bash\n   git clone https://github.com/FlowTestAI/FlowTest.git\n   ```\n\n2. Go into repository folder\n\n   ```bash\n   cd FlowTest\n   ```\n\n3. This project uses pnpm. Use [corepack](https://github.com/nodejs/corepack) to enable the required pnpm version:\n\n   ```bash\n   corepack enable pnpm\n   ```\n\n   or install with npm\n\n   ```bash\n   npm install -g pnpm@9.0.6\n   ```\n\n4. Install all project dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n5. Build and start the app:\n\n   ```bash\n   pnpm start\n   ```\n\n   The app should start as a normal desktop app\n\n   NOTE: if you use npm and corepack to install pnpm you will have two instances of pnpm. Make sure the version you're using is the correct version for the repo. Check the [pnpm docs](https://pnpm.io/installation) and [corepack](https://github.com/nodejs/corepack) for troubleshooting. Pnpm installed with npm will overrun corepacks pnpm instance.\n\n## 🤝 Contribution\n\n_\"Little drops of water make a mighty ocean\"_\n\nNo contribution is small even if it means fixing a spelling mistake. Follow our contributing guide below.\nhttps://github.com/FlowTestAI/FlowTest/blob/main/CONTRIBUTING.md\n\nFun fact: our contributing guide itself was an external contribution 🍺\n\n## 🌟 Support\n\n- ❓ QNA: feel free to ask questions, request new features or start a constructive discussion here [discussion](https://github.com/FlowTestAI/FlowTest/discussions)\n- 🐛 Issues: Feel free to raise issues here [issues](https://github.com/FlowTestAI/FlowTest/issues) (contributing guidelines coming soon..)\n- 🔄 Integration: If you want to explore how you can use this tool in your day to day activities or integrate with your existing stack or in general want to chat, you can reach out to us at any of our [social media handles](https://flowtestai.gitbook.io/flowtestai) or email me at jsajal1993@gmail.com.\n- 🔐 Our tool integrates with various leading Large Lanugage Models (LLMs) if you wish to use the natural language to flow translation feature. You can request their api keys:\n  - [OpenAI](https://platform.openai.com/)\n  - [AWS Bedrock](https://console.aws.amazon.com/bedrock/)\n  - [Google GEMINI](https://ai.google.dev/gemini-api/docs/api-key)\n  - [Local AI] (Coming Soon...)\n\n## 📜 License\n\nSource code in this repository is made available under the [MIT License](LICENSE).\n\n## Connect with Us\n\n- Website: [useflowtest.ai](https://www.useflowtest.ai/)\n- Email: jsajal1993@gmail.com\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"src\",\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ]\n  },\n  \"include\": [\n    \"src\"\n  ]\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"flowtest\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"homepage\": \".\",\n  \"packageManager\": \"pnpm@9.0.6\",\n  \"engines\": {\n    \"node\": \">=18.17.0\"\n  },\n  \"scripts\": {\n    \"start\": \"pnpm run build && cd packages/flowtest-electron && pnpm start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\",\n    \"lint\": \"eslint . --ext .js,.json,.cjs\",\n    \"lint:fix\": \"eslint --fix . --ext .js,.json,.cjs\",\n    \"format\": \"prettier --write '**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc\",\n    \"clean\": \"rm -rf node_modules/ && rm -rf packages/flowtest-electron/node_modules/\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"dependencies\": {\n    \"@codemirror/commands\": \"^6.5.0\",\n    \"@codemirror/lang-json\": \"^6.0.1\",\n    \"@codemirror/language\": \"^6.10.1\",\n    \"@codemirror/state\": \"^6.4.1\",\n    \"@codemirror/view\": \"^6.26.3\",\n    \"@emotion/react\": \"^11.11.1\",\n    \"@emotion/styled\": \"^11.11.0\",\n    \"@headlessui/react\": \"^1.7.18\",\n    \"@heroicons/react\": \"^2.1.1\",\n    \"@testing-library/jest-dom\": \"^5.17.0\",\n    \"@testing-library/react\": \"^13.4.0\",\n    \"@testing-library/user-event\": \"^13.5.0\",\n    \"@tippyjs/react\": \"^4.2.6\",\n    \"allotment\": \"^1.20.0\",\n    \"autoprefixer\": \"^10.4.18\",\n    \"axios\": \"^1.5.1\",\n    \"codemirror\": \"^6.0.1\",\n    \"date-fns\": \"^3.6.0\",\n    \"eslint-import-resolver-alias\": \"^1.1.2\",\n    \"eslint-plugin-import\": \"^2.29.1\",\n    \"form-data\": \"^4.0.0\",\n    \"immer\": \"^10.0.4\",\n    \"lodash\": \"^4.17.21\",\n    \"mousetrap\": \"^1.6.5\",\n    \"notistack\": \"^3.0.1\",\n    \"postcss\": \"^8.4.35\",\n    \"posthog-node\": \"^4.0.1\",\n    \"react\": \"^18.2.0\",\n    \"react-copy-to-clipboard\": \"^5.1.0\",\n    \"react-custom-scrollbars\": \"^4.2.1\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-edit-text\": \"^5.1.1\",\n    \"react-icons\": \"^5.0.1\",\n    \"react-json-view-lite\": \"^1.4.0\",\n    \"react-perfect-scrollbar\": \"^1.5.8\",\n    \"react-router\": \"^6.15.0\",\n    \"react-router-dom\": \"^6.22.2\",\n    \"react-scripts\": \"5.0.1\",\n    \"react-sliding-pane\": \"^7.3.0\",\n    \"react-toastify\": \"^10.0.5\",\n    \"react-tooltip\": \"^5.26.2\",\n    \"reactflow\": \"^11.8.3\",\n    \"socket.io-client\": \"^4.7.4\",\n    \"tailwindcss\": \"^3.4.1\",\n    \"typescript\": \"4\",\n    \"web-vitals\": \"^2.1.4\",\n    \"zustand\": \"^4.5.2\"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"^2.27.1\",\n    \"@tailwindcss/typography\": \"^0.5.10\",\n    \"@types/node\": \"20.11.5\",\n    \"daisyui\": \"^4.7.2\",\n    \"eslint\": \"^8.56.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-import-alias\": \"^1.2.0\",\n    \"eslint-plugin-prettier\": \"^5.1.3\",\n    \"eslint-plugin-react\": \"^7.33.2\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"prettier\": \"^3.2.5\",\n    \"prettier-plugin-tailwindcss\": \"^0.5.11\"\n  }\n}\n"
  },
  {
    "path": "packages/flowtest-cli/LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2023 Sajal Jain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/flowtest-cli/README.md",
    "content": "# flowtestai-cli\n\nWith FlowTestAI CLI, you can now run your end to end flows, constructed using FlowTestAI, directly from command line.\n\nThis makes it easier to run your tests in different environments, automate your testing process, and integrate your tests with your continuous integration and deployment workflows.\n\n## Installation\n\nTo install the FlowTestAI CLI, use the node package manager of your choice, such as NPM:\n\n```bash\nnpm install -g flowtestai\n```\n\n## Getting started\n\nNavigate to the root directory of your collection, and then run:\n\n```bash\nflow run help\n```\n\nThis command will give you various options you can use to run a flow. You can also run a single flow by specifying its filename with the `--file` or `-f` option:\n\n```bash\nflow run -f test.flow\n```\n\nOr run a requests inside a subfolder:\n\n```bash\nflow run -f folder/subfolder/test.flow\n```\n\nIf you need to use an environment, you can specify it with the `--env` or `-e` option:\n\n```bash\nflow run -f test.flow -e environments/test.env\n```\n\nIf you need to publish the results of your flow runs for further analysis, you can specify the `-s` option. Request your access key pairs from https://www.useflowtest.ai/ and then run export $FLOWTEST_ACCESS_ID and $FLOWTEST_ACCESS_KEY before publishing:\n\n```bash\nflow run -f test.flow -e environments/test.env -s\n```\n\n## Demo\n\n![demo1](assets/demo1.png)\n![demo2](assets/demo2.png)\n\n## Support\n\nIf you encounter any issues or have any feedback or suggestions, please raise them on our [GitHub repository](https://github.com/FlowTestAI/FlowTest)\n\nThank you for using FlowTestAI CLI!\n\n## Changelog\n\nSee [https://github.com/FlowTestAI/FlowTest/releases](https://github.com/FlowTestAI/FlowTest/releases)\n\n## License\n\n[MIT](LICENSE.md)\n"
  },
  {
    "path": "packages/flowtest-cli/bin/axiosClient.js",
    "content": "// lib/axiosClient.ts\nconst axios = require('axios');\nconst axiosRetry = require('axios-retry').default;\n\nconst baseUrl = 'https://www.useflowtest.ai';\n\nconst axiosClient = axios.create({\n  baseURL: `${baseUrl}/api`,\n  headers: {\n    'Content-Type': 'application/json',\n  },\n});\n\naxiosRetry(axiosClient, {\n  retries: 3, // Number of retries\n  retryDelay: (retryCount) => {\n    return retryCount * 1000; // Time interval between retries (1000 ms = 1 second)\n  },\n  retryCondition: (error) => {\n    // Retry on network errors or rate limit errors or 5xx server errors\n    return error.response?.status === 500 || error.response?.status === 429 || error.code === 'ECONNABORTED';\n  },\n});\n\nmodule.exports = {\n  baseUrl,\n  axiosClient,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/bin/index.js",
    "content": "#!/usr/bin/env node\n\nconst yargs = require('yargs/yargs');\nconst { hideBin } = require('yargs/helpers');\nconst chalk = require('chalk');\nconst readFile = require('../utils/readfile');\nconst { serialize } = require('../utils/flowparser/parser');\nconst { Graph } = require('../graph/Graph');\nconst { cloneDeep } = require('lodash');\nconst dotenv = require('dotenv');\nconst { GraphLogger, LogLevel } = require('../graph/GraphLogger');\nconst { baseUrl, axiosClient } = require('./axiosClient');\nrequire('dotenv').config();\n\nconst getEnvVariables = (pathname) => {\n  const content = readFile(pathname);\n  const buf = Buffer.from(content);\n  const parsed = dotenv.parse(buf);\n  return parsed;\n};\n\nfunction bytesToBase64(bytes) {\n  const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');\n  return btoa(binString);\n}\n\n// Define the CLI application using yargs\nconst argv = yargs(hideBin(process.argv))\n  .usage('Usage: $0 <command> [options]')\n  .command(\n    'run',\n    'Run a flow',\n    (yargs) => {\n      return yargs\n        .option('file', {\n          alias: 'f',\n          describe: 'path of the flow to run',\n          demandOption: true,\n          type: 'string',\n        })\n        .option('env', {\n          alias: 'e',\n          describe: 'path of the environment file',\n          demandOption: false,\n          type: 'string',\n        })\n        .option('timeout', {\n          alias: 't',\n          describe: 'timeout for flow run in ms',\n          demandOption: false,\n          type: 'number',\n        })\n        .option('scan', {\n          alias: 's',\n          describe: 'generate and upload flow scan',\n        });\n    },\n    async (argv) => {\n      if (argv.file.toLowerCase().endsWith(`.flow`)) {\n        let content = undefined;\n        try {\n          content = readFile(argv.file);\n        } catch (error) {\n          console.error(chalk.red(`${error}`));\n          process.exit(1);\n        }\n        try {\n          const flowData = serialize(JSON.parse(content));\n          // output json output to a file\n\n          const logger = new GraphLogger();\n          const startTime = Date.now();\n          const g = new Graph(\n            cloneDeep(flowData.nodes),\n            cloneDeep(flowData.edges),\n            startTime,\n            argv.timeout ? argv.timeout : 60000,\n            argv.env ? getEnvVariables(argv.env) : {},\n            logger,\n          );\n          console.log(chalk.blue('Running Flow \\n'));\n          console.log(\n            chalk.yellow(\n              'Right now CLI commands must be run from root directory of collection. We will gradually add support to run commands from anywhere inside the collection. \\n',\n            ),\n          );\n          const result = await g.run();\n          console.log('\\n');\n          if (result.status === 'Success') {\n            console.log(chalk.bold('Flow Run: ') + chalk.green(`   ✓ `) + chalk.dim(result.status));\n          } else {\n            console.log(chalk.bold('Flow Run: ') + chalk.red(`   ✕ `) + chalk.dim(result.status));\n          }\n          const time = Date.now() - startTime;\n          logger.add(LogLevel.INFO, `Total time: ${time} ms`);\n          console.log(chalk.bold('Total Time: ') + chalk.dim(`${time} ms`));\n          //console.log(logger.get());\n\n          if (argv.scan) {\n            const data = {\n              scan_metadata: {\n                version: 1,\n                name: argv.file.toString(),\n                status: result.status,\n                time,\n              },\n              scan: bytesToBase64(new TextEncoder().encode(JSON.stringify(logger.get()))),\n            };\n            const accessId = process.env.FLOWTEST_ACCESS_ID;\n            const accessKey = process.env.FLOWTEST_ACCESS_KEY;\n            if (!accessId || accessId.trim() === '' || !accessKey || accessKey.trim() === '') {\n              console.log(chalk.red(`   ✕ `) + chalk.dim('Unable to upload flow scan'));\n              console.log(\n                chalk.yellow(`Failed to detect access key pairs. Make sure to set environment variables properly.`),\n              );\n              console.log(chalk.yellow(`   export FLOWTEST_ACCESS_ID=\"<<FLOWTEST_ACCESS_ID>>\"`));\n              console.log(chalk.yellow(`   export FLOWTEST_ACCESS_KEY=\"<<FLOWTEST_ACCESS_KEY>>\"`));\n            } else {\n              try {\n                const response = await axiosClient.post('/upload', data, {\n                  headers: {\n                    'Content-Type': 'application/json',\n                    'x-access-id': accessId,\n                    'x-access-key': accessKey,\n                  },\n                });\n                console.log(chalk.bold('Flow Scan: ') + chalk.dim(`${baseUrl}/scan/${response.data.data[0].id}`));\n              } catch (error) {\n                if (error?.response) {\n                  if (error.response?.status >= 400 && error.response?.status < 500) {\n                    console.log(chalk.red(`   ${JSON.stringify(error.response?.data)}`));\n                  }\n\n                  if (error.response?.status === 500) {\n                    console.log(chalk.red('   Internal Server Error'));\n                  }\n                }\n                console.log(chalk.red(`   ✕ `) + chalk.dim('Unable to upload flow scan'));\n              }\n            }\n          } else {\n            console.log('\\n');\n            console.log(\n              chalk.yellow(\n                'Enable flow scans today to get more value our of your APIs. Get your access key pairs at https://www.useflowtest.ai/ \\n',\n              ),\n            );\n          }\n\n          process.exit(1);\n          //console.log(chalk.green(JSON.stringify(result)));\n        } catch (error) {\n          console.error(chalk.red(`Internal error running flow`));\n          process.exit(1);\n        }\n      } else {\n        console.error(chalk.red('Input file is not a flow file'));\n        process.exit(1);\n      }\n    },\n  )\n  .help()\n  .alias('help', 'h')\n  .parse();\n"
  },
  {
    "path": "packages/flowtest-cli/graph/Graph.js",
    "content": "// assumption is that apis are giving json as output\n\nconst { cloneDeep } = require('lodash');\nconst authNode = require('./compute/authnode');\nconst assertNode = require('./compute/assertnode');\nconst requestNode = require('./compute/requestNode');\nconst setVarNode = require('./compute/setvarnode');\nconst chalk = require('chalk');\nconst path = require('path');\nconst Node = require('./compute/node');\nconst { LogLevel } = require('./GraphLogger');\nconst readFile = require('../utils/readfile');\nconst { serialize } = require('../utils/flowparser/parser');\n\nclass nestedFlowNode extends Node {\n  constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {\n    super('flowNode');\n    try {\n      this.internalGraph = new Graph(nodes, edges, startTime, timeout, initialEnvVars, logger);\n    } catch (error) {\n      console.log(error);\n    }\n  }\n\n  async evaluate() {\n    return this.internalGraph.run();\n  }\n}\n\nclass Graph {\n  constructor(nodes, edges, startTime, timeout, initialEnvVars, logger) {\n    this.nodes = nodes;\n    this.edges = edges;\n    this.timeout = timeout;\n    this.startTime = startTime;\n    this.graphRunNodeOutput = {};\n    this.auth = undefined;\n    this.envVariables = initialEnvVars;\n    this.logger = logger;\n  }\n\n  #checkTimeout() {\n    return Date.now() - this.startTime > this.timeout;\n  }\n\n  #computeConnectingEdge(node, result) {\n    let connectingEdge = undefined;\n\n    if (node.type === 'assertNode') {\n      if (result.output === true) {\n        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'true' && edge.source === node.id);\n      } else {\n        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'false' && edge.source === node.id);\n      }\n    } else {\n      if (result.status === 'Success') {\n        connectingEdge = this.edges.find((edge) => edge.source === node.id);\n      }\n    }\n\n    return connectingEdge;\n  }\n\n  #computeDataFromPreviousNodes(node) {\n    var prevNodesData = {};\n    // a request node is allowed multiple incoming edges\n    this.edges.forEach((edge) => {\n      if (edge.target === node.id) {\n        if (this.graphRunNodeOutput[edge.source]) {\n          prevNodesData = {\n            ...prevNodesData,\n            ...this.graphRunNodeOutput[edge.source],\n          };\n        }\n      }\n    });\n    return prevNodesData;\n  }\n\n  async #computeNode(node) {\n    let result = undefined;\n    const prevNodeOutputData = this.#computeDataFromPreviousNodes(node);\n\n    try {\n      if (node.type === 'outputNode') {\n        console.log('Output Node');\n        console.log(chalk.green(`   ✓ `) + chalk.dim(`${JSON.stringify(prevNodeOutputData)}`));\n        this.logger.add(LogLevel.INFO, '', { type: 'outputNode', data: { output: prevNodeOutputData } });\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'assertNode') {\n        const eNode = new assertNode(\n          node.data.operator,\n          node.data.variables,\n          prevNodeOutputData,\n          this.envVariables,\n          this.logger,\n        );\n        if (eNode.evaluate()) {\n          console.log(chalk.green(`   ✓ `) + chalk.dim('True'));\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n            output: true,\n          };\n        } else {\n          console.log(chalk.red(`   ✕ `) + chalk.dim('False'));\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n            output: false,\n          };\n        }\n      }\n\n      if (node.type === 'delayNode') {\n        const delay = node.data.delay;\n        const wait = (ms) => {\n          return new Promise((resolve) => setTimeout(resolve, Math.min(ms, this.timeout)));\n        };\n        console.log('Delay Node: ' + chalk.green(`....waiting for: ${delay} ms`));\n        await wait(delay);\n        this.logger.add(LogLevel.INFO, '', { type: 'delayNode', data: { delay } });\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'authNode') {\n        console.log('Authentication Node');\n        const aNode = new authNode(node.data, this.envVariables, this.logger);\n        this.auth = node.data.type ? aNode.evaluate() : undefined;\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'requestNode') {\n        console.log('Request Node');\n        const rNode = new requestNode(node.data, prevNodeOutputData, this.envVariables, this.auth, this.logger);\n        result = await rNode.evaluate();\n        // add post response variables if any\n        if (result.postRespVars) {\n          this.envVariables = {\n            ...this.envVariables,\n            ...result.postRespVars,\n          };\n        }\n      }\n\n      if (node.type === 'flowNode') {\n        console.log('Flow Node (Nested graph)');\n        const content = readFile(path.join(process.cwd(), node.data.relativePath));\n        const flowData = serialize(JSON.parse(content));\n        if (flowData) {\n          const cNode = new nestedFlowNode(\n            cloneDeep(flowData.nodes),\n            cloneDeep(flowData.edges),\n            this.startTime,\n            this.timeout,\n            this.envVariables,\n            this.logger,\n          );\n          result = await cNode.evaluate();\n          this.envVariables = result.envVars;\n        } else {\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n          };\n        }\n      }\n\n      if (node.type === 'setVarNode') {\n        console.log('Set Variable Node');\n        const sNode = new setVarNode(node.data, prevNodeOutputData, this.envVariables);\n        const newVariable = sNode.evaluate();\n        if (newVariable != undefined) {\n          console.log(chalk.green(`   ✓ `) + chalk.dim(`Set variable: ${JSON.stringify(newVariable)}`));\n          this.logger.add(LogLevel.INFO, '', {\n            type: 'setVarNode',\n            data: {\n              name: Object.keys(newVariable)[0],\n              value: newVariable[Object.keys(newVariable)[0]],\n            },\n          });\n          this.envVariables = {\n            ...this.envVariables,\n            ...newVariable,\n          };\n        }\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (this.#checkTimeout()) {\n        throw Error(`Timeout of ${this.timeout} ms exceeded, stopping graph run`);\n      }\n    } catch (err) {\n      console.log(chalk.red(`Flow failed at: ${JSON.stringify(node.data)} due to ${err}`));\n      this.logger.add(LogLevel.ERROR, `Flow failed due to ${err}`, {\n        type: 'errorNode',\n        data: node.data,\n      });\n      return {\n        status: 'Failed',\n      };\n    }\n\n    if (result === undefined) {\n      console.log(chalk.red(`Flow failed due to failure to evaluate result at node: ${node.data}`));\n      this.logger.add(LogLevel.ERROR, 'Flow failed due to failure to evaluate result', {\n        type: 'errorNode',\n        data: node.data,\n      });\n      return {\n        status: 'Failed',\n      };\n    } else {\n      const connectingEdge = this.#computeConnectingEdge(node, result);\n\n      if (connectingEdge != undefined) {\n        const nextNode = this.nodes.find(\n          (node) =>\n            ['requestNode', 'outputNode', 'assertNode', 'delayNode', 'authNode', 'flowNode', 'setVarNode'].includes(\n              node.type,\n            ) && node.id === connectingEdge.target,\n        );\n        this.graphRunNodeOutput[node.id] = result.data ? result.data : {};\n        return this.#computeNode(nextNode);\n      } else {\n        return result;\n      }\n    }\n  }\n\n  async run() {\n    this.graphRunNodeOutput = {};\n\n    console.log(chalk.green('Start Flowtest'));\n    this.logger.add(LogLevel.INFO, 'Start Flowtest');\n\n    const startNode = this.nodes.find((node) => node.type === 'startNode');\n    if (startNode == undefined) {\n      console.log(chalk.red(`✕ `) + chalk.red('No start node found'));\n      console.log(chalk.red('End Flowtest'));\n      this.logger.add(LogLevel.INFO, 'No start node found');\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: 'Success',\n        envVars: this.envVariables,\n      };\n    }\n    const connectingEdge = this.edges.find((edge) => edge.source === startNode.id);\n\n    // only start computing graph if initial node has the connecting edge\n    if (connectingEdge != undefined) {\n      const firstNode = this.nodes.find((node) => node.id === connectingEdge.target);\n      const result = await this.#computeNode(firstNode);\n      if (result.status == 'Failed') {\n        console.log(chalk.red('End Flowtest'));\n      } else {\n        console.log(chalk.green('End Flowtest'));\n      }\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: result.status,\n        envVars: this.envVariables,\n      };\n    } else {\n      console.log(chalk.green('End Flowtest'));\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: 'Success',\n        envVars: this.envVariables,\n      };\n    }\n  }\n}\n\nmodule.exports = { Graph };\n"
  },
  {
    "path": "packages/flowtest-cli/graph/GraphLogger.js",
    "content": "const LogLevel = Object.freeze({\n  INFO: 'info',\n  WARN: 'warn',\n  ERROR: 'error',\n});\n\nclass GraphLogger {\n  constructor() {\n    this.logs = [];\n  }\n\n  add(logLevel, message, node) {\n    this.logs.push({\n      level: logLevel,\n      timestamp: new Date().toISOString(),\n      message,\n      node,\n    });\n  }\n\n  get() {\n    return this.logs;\n  }\n}\n\nmodule.exports = {\n  GraphLogger,\n  LogLevel,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/assertnode.js",
    "content": "const AssertOperators = require('../constants/assertOperators');\nconst { computeNodeVariable } = require('./utils');\nconst Node = require('./node');\nconst chalk = require('chalk');\nconst { LogLevel } = require('../GraphLogger');\n\nclass assertNode extends Node {\n  constructor(operator, variables, prevNodeOutputData, envVariables, logger) {\n    super('assertNode');\n    this.operator = operator;\n    this.variables = variables;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.logger = logger;\n    this.envVariables = envVariables;\n  }\n\n  getVariableValue(variable) {\n    if (variable.type.toLowerCase() === 'variable') {\n      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {\n        return this.envVariables[variable.value];\n      } else {\n        throw Error(`Cannot find value of variable ${variable.value}`);\n      }\n    } else {\n      return computeNodeVariable(variable, this.prevNodeOutputData);\n    }\n  }\n\n  evaluate() {\n    //console.log('Evaluating an assert node');\n    const var1 = this.getVariableValue(this.variables.var1);\n    const var2 = this.getVariableValue(this.variables.var2);\n\n    const operator = this.operator;\n    if (operator == undefined) {\n      throw Error('Operator undefined');\n    }\n    // this.logs.push(\n    //   `Assert var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,\n    // );\n    console.log(\n      'Assert Node: ' +\n        chalk.green(\n          `Assert var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,\n        ),\n    );\n    let result;\n    switch (operator) {\n      case AssertOperators.isEqualTo:\n        result = var1 === var2;\n        break;\n      case AssertOperators.isNotEqualTo:\n        result = var1 != var2;\n        break;\n      case AssertOperators.isGreaterThan:\n        result = var1 > var2;\n        break;\n      case AssertOperators.isLessThan:\n        result = var1 < var2;\n        break;\n      default:\n        throw Error('Unsupported operator');\n    }\n    this.logger.add(LogLevel.INFO, '', { type: 'assertNode', data: { var1, var2, operator, result } });\n\n    return result;\n  }\n}\n\nmodule.exports = assertNode;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/authnode.js",
    "content": "const { computeVariables } = require('./utils');\nconst Node = require('./node');\nconst chalk = require('chalk');\nconst { LogLevel } = require('../GraphLogger');\n\nclass authNode extends Node {\n  constructor(nodeData, envVariables, logger) {\n    super('authNode');\n    (this.nodeData = nodeData), (this.envVariables = envVariables);\n    this.logger = logger;\n  }\n\n  evaluate() {\n    //console.log('Evaluating an auth node');\n    if (this.nodeData.type === 'basic-auth') {\n      console.log(chalk.green(`   ✓ `) + chalk.dim('.....setting basic authentication'));\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Basic Authentication' } });\n      const username = computeVariables(this.nodeData.username, this.envVariables);\n      const password = computeVariables(this.nodeData.password, this.envVariables);\n\n      return {\n        type: 'basic-auth',\n        username,\n        password,\n      };\n    } else if (this.nodeData.type === 'bearer-token') {\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Bearer Token' } });\n      const token = computeVariables(this.nodeData.token, this.envVariables);\n      return {\n        type: 'bearer-token',\n        token,\n      };\n    } else if (this.nodeData.type === 'no-auth') {\n      console.log(chalk.green(`   ✓ `) + chalk.dim('.....using no authentication'));\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'No Authentication' } });\n      return {\n        type: 'no-auth',\n      };\n    } else {\n      throw Error(`auth type: ${this.nodeData.type} is not valid`);\n    }\n  }\n}\n\nmodule.exports = authNode;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/node.js",
    "content": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  evaluate() {\n    throw new Error('Evaluate method must be implemented by subclasses');\n  }\n}\n\nmodule.exports = Node;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/requestnode.js",
    "content": "const { computeNodeVariables, computeVariables } = require('./utils');\nconst Node = require('./node');\nconst axios = require('axios');\nconst chalk = require('chalk');\nconst { LogLevel } = require('../GraphLogger');\nconst FormData = require('form-data');\nconst { extend, cloneDeep } = require('lodash');\nconst fs = require('fs');\nconst path = require('path');\n\nconst newAbortSignal = () => {\n  const abortController = new AbortController();\n  setTimeout(() => abortController.abort(), 60000 || 0);\n\n  return abortController.signal;\n};\n\n/** web platform: blob. */\nconst convertBase64ToBlob = async (base64) => {\n  const response = await fetch(base64);\n  const blob = await response.blob();\n  return blob;\n};\n\nclass requestNode extends Node {\n  constructor(nodeData, prevNodeOutputData, envVariables, auth, logger) {\n    super('requestNode');\n    this.nodeData = nodeData;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.envVariables = envVariables;\n    this.auth = auth;\n    this.logger = logger;\n  }\n\n  async evaluate() {\n    // step1 evaluate pre request variables of this node\n    const evalVariables = computeNodeVariables(this.nodeData.preReqVars, this.prevNodeOutputData);\n\n    const variablesDict = {\n      ...this.envVariables,\n      ...evalVariables,\n    };\n\n    // step2 replace variables in url with value\n    const finalUrl = computeVariables(this.nodeData.url, variablesDict);\n\n    // step 3\n    const rawRequest = this.formulateRequest(finalUrl, variablesDict);\n\n    console.log(chalk.green(`   ✓ `) + chalk.dim(`type = ${this.nodeData.requestType.toUpperCase()}`));\n    console.log(chalk.green(`   ✓ `) + chalk.dim(`url = ${finalUrl}`));\n\n    const { request, response } = await this.runHttpRequest(rawRequest);\n\n    if (response.error) {\n      console.log(chalk.red(`   ✕ `) + chalk.dim(`Request failed: ${JSON.stringify(response.error)}`));\n      this.logger.add(LogLevel.ERROR, 'HTTP request failed', {\n        type: 'requestNode',\n        data: {\n          request,\n          response: response.error,\n          preReqVars: evalVariables,\n        },\n      });\n      return {\n        status: 'Failed',\n      };\n    } else {\n      console.log(chalk.green(`   ✓ `) + chalk.dim(`Request successful: ${JSON.stringify(response)}`));\n      if (this.nodeData.postRespVars) {\n        const evalPostRespVars = computeNodeVariables(this.nodeData.postRespVars, response.data);\n        this.logger.add(LogLevel.INFO, 'HTTP request success', {\n          type: 'requestNode',\n          data: {\n            request,\n            response,\n            preReqVars: evalVariables,\n            postRespVars: evalPostRespVars,\n          },\n        });\n        return {\n          status: 'Success',\n          data: response.data,\n          postRespVars: evalPostRespVars,\n        };\n      }\n      this.logger.add(LogLevel.INFO, 'HTTP request success', {\n        type: 'requestNode',\n        data: {\n          request,\n          response,\n          preReqVars: evalVariables,\n        },\n      });\n      return {\n        status: 'Success',\n        data: response.data,\n      };\n    }\n  }\n\n  formulateRequest(finalUrl, variablesDict) {\n    let restMethod = this.nodeData.requestType.toLowerCase();\n    let headers = {};\n    let requestData = undefined;\n\n    if (this.nodeData.requestBody) {\n      if (this.nodeData.requestBody.type === 'raw-json') {\n        headers['content-type'] = 'application/json';\n        requestData = this.nodeData.requestBody.body\n          ? JSON.parse(computeVariables(this.nodeData.requestBody.body, variablesDict))\n          : JSON.parse('{}');\n      } else if (this.nodeData.requestBody.type === 'form-data') {\n        headers['content-type'] = 'multipart/form-data';\n        const params = cloneDeep(this.nodeData.requestBody.body);\n        requestData = params;\n      }\n    }\n\n    if (this.nodeData.headers && this.nodeData.headers.length > 0) {\n      this.nodeData.headers.map((pair, index) => {\n        headers[computeVariables(pair.name, variablesDict)] = computeVariables(pair.value, variablesDict);\n      });\n    }\n\n    if (this.auth && this.auth?.type === 'bearer-token') {\n      headers['Authorization'] = `Bearer ${this.auth.token}`;\n    }\n\n    const options = {\n      method: restMethod,\n      url: finalUrl,\n      headers,\n      data: requestData,\n    };\n\n    if (this.auth && this.auth?.type === 'basic-auth') {\n      options.auth = {};\n      options.auth.username = this.auth.username;\n      options.auth.password = this.auth.password;\n    }\n\n    return options;\n  }\n\n  async runHttpRequest(request) {\n    let requestSent;\n    try {\n      if (request.headers['content-type'] === 'multipart/form-data') {\n        const formData = new FormData();\n        const params = request.data;\n        await params.map(async (param, index) => {\n          if (param.type === 'text') {\n            formData.append(param.key, param.value);\n          }\n\n          if (param.type === 'file') {\n            let trimmedFilePath = param.value.trim();\n\n            formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));\n          }\n        });\n\n        request.data = formData;\n        extend(request.headers, formData.getHeaders());\n      }\n\n      requestSent = {\n        url: request.url,\n        method: request.method,\n        headers: request.headers,\n        // form data obj gets serialized here so that it can be sent over wire\n        // otherwise ipc communication errors out\n        data: request.data ? JSON.parse(JSON.stringify(request.data)) : request.data,\n      };\n\n      const result = await axios({\n        ...request,\n        signal: newAbortSignal(),\n      });\n\n      return {\n        request: requestSent,\n        response: {\n          status: result.status,\n          statusText: result.statusText,\n          data: result.data,\n          headers: result.headers,\n        },\n      };\n    } catch (error) {\n      if (error?.response) {\n        return {\n          request: requestSent,\n          response: {\n            error: {\n              status: error.response.status,\n              statusText: error.response.statusText,\n              data: error.response.data,\n              headers: error.response.headers,\n            },\n          },\n        };\n      } else {\n        return {\n          request: requestSent,\n          response: {\n            error: {\n              status: '',\n              statusText: '',\n              data: `An error occurred while running the request : ${error?.message}`,\n            },\n          },\n        };\n      }\n    }\n  }\n}\n\nmodule.exports = requestNode;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/setvarnode.js",
    "content": "const { computeNodeVariable } = require('./utils');\nconst Node = require('./node');\nconst EvaluateOperators = require('../constants/evaluateOperators');\n\nclass setVarNode extends Node {\n  constructor(nodeData, prevNodeOutputData, envVariables) {\n    super('setVarNode');\n    this.nodeData = nodeData;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.envVariables = envVariables;\n  }\n\n  getVariableValue(variable) {\n    if (variable.type.toLowerCase() === 'variable') {\n      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {\n        return this.envVariables[variable.value];\n      } else {\n        throw Error(`Cannot find value of variable ${variable.value}`);\n      }\n    } else {\n      return computeNodeVariable(variable, this.prevNodeOutputData);\n    }\n  }\n\n  evaluate() {\n    //console.log('Evaluating set variable node');\n    if (this.nodeData.variable) {\n      if (this.nodeData.variable.name && this.nodeData.variable.name.trim() != '') {\n        const vName = this.nodeData.variable.name;\n        const vType = this.nodeData.variable.type.trim();\n        if (['String', 'Number', 'Boolean', 'Now', 'Select'].includes(vType)) {\n          const value = computeNodeVariable(this.nodeData.variable, this.prevNodeOutputData);\n          return {\n            [vName]: value,\n          };\n        } else if (vType === 'Expression') {\n          const variables = this.nodeData.variable.value.variables;\n          if (variables && variables.var1 && variables.var2) {\n            const var1 = this.getVariableValue(this.nodeData.variable.value.variables.var1);\n            const var2 = this.getVariableValue(this.nodeData.variable.value.variables.var2);\n\n            const operator = this.nodeData.variable.value.operator;\n            if (operator == undefined) {\n              throw 'Operator undefined';\n            }\n            if (operator == EvaluateOperators.Add) {\n              if (typeof var1 !== 'number' || typeof var2 !== 'number') {\n                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);\n              }\n              return {\n                [vName]: var1 + var2,\n              };\n            } else if (operator == EvaluateOperators.Subtract) {\n              if (typeof var1 !== 'number' || typeof var2 !== 'number') {\n                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);\n              }\n              return {\n                [vName]: var1 - var2,\n              };\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nmodule.exports = setVarNode;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/utils.js",
    "content": "const computeNodeVariable = (variable, prevNodeOutputData) => {\n  if (variable.type.toLowerCase() === 'string') {\n    return variable.value.toString();\n  }\n\n  if (variable.type.toLowerCase() === 'number') {\n    return Number(variable.value);\n  }\n\n  if (variable.type.toLowerCase() === 'boolean') {\n    return Boolean(variable.value);\n  }\n\n  if (variable.type.toLowerCase() === 'now') {\n    return Date.now();\n  }\n\n  if (variable.type.toLowerCase() === 'select') {\n    try {\n      if (prevNodeOutputData == undefined || Object.keys(prevNodeOutputData).length === 0) {\n        console.debug(\n          `Cannot evaluate variable ${variable} as previous node output data ${JSON.stringify(prevNodeOutputData)} is empty`,\n        );\n        throw Error(`Cannot evaluate variable ${variable.value}`);\n      }\n      const jsonTree = variable.value.split('.');\n      const getVal = (parent, pos) => {\n        if (pos == jsonTree.length) {\n          return parent;\n        }\n        const key = jsonTree[pos];\n        if (key == '') {\n          return parent;\n        }\n\n        return getVal(parent[key], pos + 1);\n      };\n      const result = getVal(prevNodeOutputData, 0);\n      if (result == undefined) {\n        console.debug(\n          `Cannot evaluate variable ${JSON.stringify(variable)} as previous node output data ${JSON.stringify(prevNodeOutputData)} did not contain the variable`,\n        );\n        throw Error(`Cannot evaluate variable ${variable.value}`);\n      }\n      return result;\n    } catch (error) {\n      throw Error(`Cannot evaluate variable ${variable.value}`);\n    }\n  }\n};\n\nconst computeNodeVariables = (variables, prevNodeOutputData) => {\n  const evalVariables = {};\n  if (variables) {\n    Object.entries(variables).map(([vname, variable]) => {\n      evalVariables[vname] = computeNodeVariable(variable, prevNodeOutputData);\n    });\n  }\n  return evalVariables;\n};\n\nconst computeVariables = (str, variablesDict) => {\n  const regex = /\\{\\{(.+)\\}\\}/;\n  const foundRegex = regex.exec(str);\n  if (foundRegex) {\n    const match = str.match(/{{([^}]+)}}/);\n    if (variablesDict) {\n      if (Object.prototype.hasOwnProperty.call(variablesDict, match[1])) {\n        const varValue = variablesDict[`${match[1]}`];\n        return computeVariables(str.replaceAll(match[0], varValue), variablesDict);\n      } else {\n        throw Error(`Cannot find value of variable ${match[1]}`);\n      }\n    } else {\n      throw Error(`Cannot compute variable ${match[1]} as dict is empty`);\n    }\n  } else {\n    return str;\n  }\n};\n\nmodule.exports = {\n  computeNodeVariable,\n  computeNodeVariables,\n  computeVariables,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/graph/compute/utils.test.js",
    "content": "const { computeVariables } = require('./utils');\n\ndescribe('Utils', () => {\n  it('should compute variables correctly', () => {\n    let str = 'hello {{var1}}! hello {{var1}}! bye';\n    let dict = {\n      var1: 'world',\n    };\n\n    let result = computeVariables(str, dict);\n    expect(result).toEqual('hello world! hello world! bye');\n\n    str = 'hello {{var1}}! hello {{var2}}! bye';\n    expect(() => {\n      computeVariables(str, dict);\n    }).toThrow(Error);\n\n    str = 'hello {{var1}}! hello {{var2}}! bye';\n    dict = null;\n    expect(() => {\n      computeVariables(str, dict);\n    }).toThrow(Error);\n\n    dict = {\n      var1: 'world',\n      var2: 'person',\n    };\n    result = computeVariables(str, dict);\n    expect(result).toEqual('hello world! hello person! bye');\n\n    str = 'hello world!';\n    result = computeVariables(str, dict);\n    expect(result).toEqual(str);\n  });\n});\n"
  },
  {
    "path": "packages/flowtest-cli/graph/constants/assertOperators.js",
    "content": "const AssertOperators = {\n  isLessThan: 'isLessThan',\n  isGreaterThan: 'isGreaterThan',\n  isEqualTo: 'isEqualTo',\n  isNotEqualTo: 'isNotEqualTo',\n};\n\nmodule.exports = AssertOperators;\n"
  },
  {
    "path": "packages/flowtest-cli/graph/constants/evaluateOperators.js",
    "content": "const EvaluateOperators = {\n  Add: 'Add two numbers',\n  Subtract: 'Subtract two numbers',\n};\n\nmodule.exports = EvaluateOperators;\n"
  },
  {
    "path": "packages/flowtest-cli/package.json",
    "content": "{\n  \"name\": \"flowtestai\",\n  \"version\": \"1.0.2\",\n  \"description\": \"CLI to run flow from command line\",\n  \"main\": \"bin/index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"bin\": {\n    \"flow\": \"bin/index.js\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/FlowTestAI/FlowTest/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/FlowTestAI/FlowTest.git\"\n  },\n  \"author\": \"Sajal Jain <jsajal1993@gmail.com>\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"axios\": \"^1.7.2\",\n    \"axios-retry\": \"^4.4.0\",\n    \"boxen\": \"^7.1.1\",\n    \"chalk\": \"^3.0.0\",\n    \"dotenv\": \"^16.4.5\",\n    \"form-data\": \"^4.0.0\",\n    \"fs\": \"^0.0.1-security\",\n    \"lodash\": \"^4.17.21\",\n    \"omelette\": \"^0.4.17\",\n    \"path\": \"^0.12.7\",\n    \"yargs\": \"^17.7.2\"\n  },\n  \"engines\": {\n    \"node\": \">=18.17.0\"\n  }\n}\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/AssertNode.js",
    "content": "const { Node } = require('./Node');\n\nclass AssertNode extends Node {\n  constructor() {\n    super('assertNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  AssertNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/AuthNode.js",
    "content": "const { Node } = require('./Node');\n\nclass AuthNode extends Node {\n  constructor() {\n    super('authNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  AuthNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/DelayNode.js",
    "content": "const { Node } = require('./Node');\n\nclass DelayNode extends Node {\n  constructor() {\n    super('delayNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  DelayNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/NestedFlowNode.js",
    "content": "const { Node } = require('./Node');\n\nclass NestedFlowNode extends Node {\n  constructor() {\n    super('flowNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  NestedFlowNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/Node.js",
    "content": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  serialize(id, data, metadata) {\n    throw new Error('Serialize method must be implemented by subclasses');\n  }\n\n  deserialize(node) {\n    throw new Error('Deserialize method must be implemented by subclasses');\n  }\n}\n\nmodule.exports = {\n  Node,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/OutputNode.js",
    "content": "const { Node } = require('./Node');\n\nclass OutputNode extends Node {\n  constructor() {\n    super('outputNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const { ['output']: _, ...data } = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  OutputNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/RequestNode.js",
    "content": "const { Node } = require('./Node');\n\nclass RequestNode extends Node {\n  constructor() {\n    super('requestNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  RequestNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/SetVarNode.js",
    "content": "const { Node } = require('./Node');\n\nclass SetVarNode extends Node {\n  constructor() {\n    super('setVarNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  SetVarNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/StartNode.js",
    "content": "const { Node } = require('./Node');\n\nclass StartNode extends Node {\n  constructor() {\n    super('startNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    delete node.id;\n    const metadata = node;\n\n    return {\n      id,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  StartNode,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/flowparser/parser.js",
    "content": "const { cloneDeep } = require('lodash');\nconst { AuthNode } = require('./AuthNode');\nconst { NestedFlowNode } = require('./NestedFlowNode');\nconst { DelayNode } = require('./DelayNode');\nconst { AssertNode } = require('./AssertNode');\nconst { OutputNode } = require('./OutputNode');\nconst { RequestNode } = require('./RequestNode');\nconst { StartNode } = require('./StartNode');\nconst { SetVarNode } = require('./SetVarNode');\n\nconst VERSION = 1;\n\nconst deserialize = (flowData) => {\n  // we don't want to modify original object\n  const flowDataCopy = cloneDeep(flowData);\n\n  const textData = {};\n  textData.version = VERSION;\n  textData.graph = {};\n\n  if (flowData) {\n    if (flowData.nodes) {\n      const nodes = flowDataCopy.nodes;\n      textData.graph.data = {};\n      textData.graph.data.nodes = {};\n      textData.graph.metadata = {};\n      textData.graph.metadata.nodes = {};\n\n      nodes.forEach((node) => {\n        if (node.type === 'startNode') {\n          const sNode = new StartNode();\n          const result = sNode.deserialize(node);\n\n          textData.graph.data.nodes[result.id] = {\n            type: 'startNode',\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'startNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'authNode') {\n          const aNode = new AuthNode();\n          const result = aNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'authNode',\n            auth: result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'authNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'requestNode') {\n          const rNode = new RequestNode();\n          const result = rNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'requestNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'requestNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'outputNode') {\n          const oNode = new OutputNode();\n          const result = oNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'outputNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'outputNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'delayNode') {\n          const dNode = new DelayNode();\n          const result = dNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'delayNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'delayNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'assertNode') {\n          const eNode = new AssertNode();\n          const result = eNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'assertNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'assertNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'flowNode') {\n          const fNode = new NestedFlowNode();\n          const result = fNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'flowNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'flowNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'setVarNode') {\n          const sNode = new SetVarNode();\n          const result = sNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'setVarNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'setVarNode',\n            ...result.metadata,\n          };\n        }\n      });\n    }\n\n    if (flowData.edges) {\n      const edges = flowDataCopy.edges;\n      textData.graph.data.edges = [];\n      textData.graph.metadata.edges = {};\n\n      edges.forEach((edge) => {\n        textData.graph.data.edges.push(`${edge.source} -> ${edge.target}`);\n\n        const { ['id']: _, ..._edge } = edge;\n        textData.graph.metadata.edges[edge.id] = _edge;\n      });\n    }\n\n    if (flowData.viewport) {\n      textData.graph.metadata.viewport = flowDataCopy.viewport;\n    }\n  }\n\n  return textData;\n};\n\nconst serialize = (textData) => {\n  const flowData = {};\n  flowData.nodes = [];\n  flowData.edges = [];\n  flowData.viewport = { x: 0, y: 0, zoom: 1 };\n\n  // we don't want to modify original object\n  const textDataCopy = cloneDeep(textData);\n  const version = textDataCopy.version;\n  if (version === 1) {\n    if (textDataCopy.graph.data) {\n      Object.entries(textDataCopy.graph.data.nodes).map(([key, value], index) => {\n        const id = key;\n\n        if (value.type === 'startNode') {\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const sNode = new StartNode();\n          const result = sNode.serialize(id, undefined, metadata);\n\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'authNode') {\n          const data = value.auth;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const aNode = new AuthNode();\n          const result = aNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'requestNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const rNode = new RequestNode();\n          const result = rNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'outputNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const oNode = new OutputNode();\n          const result = oNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'delayNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const dNode = new DelayNode();\n          const result = dNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'assertNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const dNode = new AssertNode();\n          const result = dNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'flowNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const fNode = new NestedFlowNode();\n          const result = fNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'setVarNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const cNode = new SetVarNode();\n          const result = cNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n      });\n\n      Object.entries(textDataCopy.graph.metadata.edges).map(([key, value], index) => {\n        flowData.edges.push({\n          id: key,\n          ...value,\n        });\n      });\n\n      if (textDataCopy.graph.metadata.viewport) {\n        flowData.viewport = textDataCopy.graph.metadata.viewport;\n      }\n    }\n  } else {\n    throw new Error('Version not recognized');\n  }\n\n  return flowData;\n};\n\nmodule.exports = {\n  deserialize,\n  serialize,\n};\n"
  },
  {
    "path": "packages/flowtest-cli/utils/readfile.js",
    "content": "const fs = require('fs');\n\nconst pathExists = (path) => {\n  try {\n    fs.accessSync(path);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n\nconst readFile = (path) => {\n  if (!path) {\n    throw new Error('File path is required');\n  }\n\n  // check if file exists\n  if (!pathExists(path)) {\n    throw new Error('File does not exist');\n  }\n\n  // now delete the file\n  return fs.readFileSync(path, 'utf8');\n};\n\nmodule.exports = readFile;\n"
  },
  {
    "path": "packages/flowtest-electron/.npmrc",
    "content": "node-linker=hoisted #pnpm requirement when working with electron"
  },
  {
    "path": "packages/flowtest-electron/CHANGELOG.md",
    "content": "# flowtestai\n\n## 1.2.0\n\n### Minor Changes\n\n- aadbd1b: Collapse sidebar to give more real estate to canvas\n- 780f3c2: generate sample request body and parameter values\n- e968f1e: beautify logs sidesheet and ability to upload flow scans\n- 92a0bab: Add support for anthropic claude hosted on bedrock\n- 97c6981: display pretty structured logs in graph run\n- 6def317: check version mismatch on startup and notify user of latest version availability\n- 5a92053: Ability to manage multiple flow tabs simultaneously\n- 563e011: Allow multiple kv params in multipart form data request type\n- a07dbea: allow configurable user settings\n- d20fb8b: make app platform agnostic to allow windows platform support\n- 912483f: use rich editor for auto complete variables and redefine UI of request nodes\n\n## 1.1.0\n\n### Minor Changes\n\n- a5d61a8: Introduce a UI theme, json editor powered by codemirror and few shortcut keys to improve workflow\n- 7f45adc: A more intuitive UX to onboard first time user\n- 95310af: Updated contributing docs and linting\n- 19c52aa: Ability to clone a fow and expand output node for bigger view\n- 5ac8237: Maintain state of logs and viewports of each canvas separately.\n"
  },
  {
    "path": "packages/flowtest-electron/electron-main.js",
    "content": "// Modules to control application life and create native browser window\nconst { app, BrowserWindow, Menu, shell } = require('electron');\nconst path = require('path');\nconst url = require('url');\nconst template = require('./electron-menu');\nconst Watcher = require('./src/app/watcher');\nconst registerRendererEventHandlers = require('./src/ipc/collection');\nconst registerSettingsEventHandlers = require('./src/ipc/settings');\nconst packageJson = require('./package.json'); // app's package.json\nconst https = require('https');\n\nlet mainWindow;\nlet watcher;\n\nif (process.env.NODE_ENV === 'production') {\n  const noop = () => {};\n  console.log = noop;\n  console.info = noop;\n  console.error = noop;\n  console.warn = noop;\n  console.debug = noop;\n  console.trace = noop;\n}\n\nconst version = {\n  current: packageJson.version,\n  latest: packageJson.version,\n};\n\nfunction checkForUpdates() {\n  const url = `https://raw.githubusercontent.com/FlowTestAI/FlowTest/main/packages/flowtest-electron/package.json`;\n\n  https\n    .get(url, (res) => {\n      let data = '';\n\n      res.on('data', (chunk) => {\n        data += chunk;\n      });\n\n      res.on('end', () => {\n        try {\n          const remotePackageJson = JSON.parse(data);\n          const latestVersion = remotePackageJson.version;\n\n          if (latestVersion !== version.current) {\n            version.latest = latestVersion;\n            //shell.openExternal(`https://github.com/${username}/${repo}/releases`);\n          }\n        } catch (error) {\n          console.error('Error parsing JSON:', error);\n        }\n      });\n    })\n    .on('error', (err) => {\n      console.error('Error fetching package.json:', err);\n    });\n}\n\napp.on('ready', async () => {\n  const menu = Menu.buildFromTemplate(template);\n  Menu.setApplicationMenu(menu);\n\n  // Create the browser window.\n  mainWindow = new BrowserWindow({\n    width: 1280,\n    height: 768,\n    icon: path.join(__dirname, 'assets/MyIcon.png'),\n    webPreferences: {\n      nodeIntegration: true,\n      contextIsolation: true,\n      preload: path.join(__dirname, 'preload.js'),\n      webviewTag: true,\n    },\n    title: 'FlowTestAI',\n  });\n\n  mainWindow.maximize();\n\n  // and load the index.html of the app.\n  const startUrl = url.format({\n    pathname: path.join(__dirname, '../../build/index.html'),\n    protocol: 'file:',\n    slashes: true,\n  });\n  mainWindow.loadURL(startUrl);\n\n  // Open the DevTools.\n  // mainWindow.webContents.openDevTools()\n\n  // This is required to open a link in the external browser\n  mainWindow.webContents.setWindowOpenHandler(({ url }) => {\n    shell.openExternal(url);\n    return { action: 'deny' };\n  });\n\n  watcher = new Watcher();\n\n  checkForUpdates();\n  mainWindow.webContents.on('did-finish-load', () => {\n    // Send a message to the renderer process\n    mainWindow.webContents.send('main:app-version', version);\n  });\n\n  registerRendererEventHandlers(mainWindow, watcher);\n  registerSettingsEventHandlers(mainWindow);\n});\n\n// Quit when all windows are closed, except on macOS. There, it's common\n// for applications and their menu bar to stay active until the user quits\n// explicitly with Cmd + Q.\napp.on('window-all-closed', function () {\n  //if (process.platform !== 'darwin')\n  app.quit();\n});\n"
  },
  {
    "path": "packages/flowtest-electron/electron-menu.js",
    "content": "const { shell } = require('electron');\n\nconst template = [\n  {\n    label: 'FlowTestAI',\n    submenu: [\n      { type: 'separator' },\n      {\n        role: 'quit',\n        label: 'Exit FlowTestAI',\n      },\n    ],\n  },\n  {\n    label: 'Edit',\n    submenu: [\n      { role: 'undo' },\n      { role: 'redo' },\n      { type: 'separator' },\n      { role: 'cut' },\n      { role: 'copy' },\n      { role: 'paste' },\n      { role: 'selectAll' },\n      { type: 'separator' },\n      { role: 'hide' },\n      { role: 'hideOthers' },\n    ],\n  },\n  {\n    label: 'View',\n    submenu: [\n      { role: 'toggledevtools' },\n      { type: 'separator' },\n      { role: 'resetzoom' },\n      { role: 'zoomin' },\n      { role: 'zoomout' },\n      { type: 'separator' },\n      { role: 'togglefullscreen' },\n    ],\n  },\n  {\n    role: 'window',\n    submenu: [{ role: 'minimize' }, { role: 'close', accelerator: 'CommandOrControl+Shift+Q' }],\n  },\n  {\n    role: 'help',\n    label: 'Help',\n    submenu: [\n      {\n        label: 'About',\n        click: async () => {\n          await shell.openExternal('https://github.com/FlowTestAI/FlowTest');\n        },\n      },\n    ],\n  },\n];\n\nmodule.exports = template;\n"
  },
  {
    "path": "packages/flowtest-electron/notarize.js",
    "content": "require('dotenv').config();\nconst { notarize } = require('@electron/notarize');\n\nexports.default = async function notarizing(context) {\n  const { electronPlatformName, appOutDir } = context;\n  if (electronPlatformName !== 'darwin') {\n    return;\n  }\n\n  const appName = context.packager.appInfo.productFilename;\n\n  return await notarize({\n    appBundleId: 'com.flowtestai.app',\n    appPath: `${appOutDir}/${appName}.app`,\n    appleId: process.env.APPLE_ID,\n    appleIdPassword: process.env.APPLE_ID_PASSWORD,\n    teamId: process.env.TEAM_ID,\n  });\n};\n"
  },
  {
    "path": "packages/flowtest-electron/package.json",
    "content": "{\n  \"name\": \"flowtestai-app\",\n  \"productName\": \"FlowTestAI\",\n  \"version\": \"1.2.0\",\n  \"homepage\": \"https://github.com/FlowTestAI/FlowTest/tree/main\",\n  \"description\": \"GenAI powered OpenSource IDE for API first workflows\",\n  \"main\": \"electron-main.js\",\n  \"bugs\": {\n    \"url\": \"https://github.com/FlowTestAI/FlowTest/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=18.17.0\"\n  },\n  \"scripts\": {\n    \"start\": \"electron .\",\n    \"test\": \"jest\",\n    \"pack\": \"NODE_ENV=production electron-builder --dir\",\n    \"dist:mac\": \"NODE_ENV=production electron-builder --mac\",\n    \"dist:win\": \"SET NODE_ENV=production & electron-builder --win\"\n  },\n  \"author\": \"Sajal Jain <jsajal1993@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@electron/notarize\": \"^2.3.0\",\n    \"electron\": \"^29.0.0\",\n    \"electron-builder\": \"^24.13.3\",\n    \"jest\": \"^29.7.0\"\n  },\n  \"dependencies\": {\n    \"@apidevtools/swagger-parser\": \"^10.1.0\",\n    \"@aws-crypto/sha256-js\": \"^5.2.0\",\n    \"@aws-sdk/client-bedrock\": \"^3.583.0\",\n    \"@aws-sdk/client-bedrock-runtime\": \"^3.583.0\",\n    \"@aws-sdk/credential-provider-node\": \"^3.583.0\",\n    \"@aws-sdk/types\": \"^3.577.0\",\n    \"@google/generative-ai\": \"^0.16.0\",\n    \"@langchain/community\": \"^0.2.19\",\n    \"@langchain/google-genai\": \"^0.0.25\",\n    \"@smithy/eventstream-codec\": \"^3.0.0\",\n    \"@smithy/protocol-http\": \"^4.0.0\",\n    \"@smithy/signature-v4\": \"^3.0.0\",\n    \"@smithy/util-utf8\": \"^3.0.0\",\n    \"axios\": \"^1.6.7\",\n    \"axios-retry\": \"^4.4.0\",\n    \"chokidar\": \"^3.6.0\",\n    \"dotenv\": \"^16.4.5\",\n    \"electron-store\": \"^8.1.0\",\n    \"flatted\": \"^3.3.1\",\n    \"form-data\": \"^4.0.0\",\n    \"fs\": \"^0.0.1-security\",\n    \"json-refs\": \"^3.0.15\",\n    \"langchain\": \"^0.1.28\",\n    \"lodash\": \"^4.17.21\",\n    \"openai\": \"^4.29.1\",\n    \"path\": \"^0.12.7\",\n    \"uuid\": \"^9.0.1\"\n  },\n  \"build\": {\n    \"appId\": \"com.flowtestai.app\",\n    \"productName\": \"FlowTestAI\",\n    \"directories\": {\n      \"buildResources\": \"resources\",\n      \"output\": \"dist\"\n    },\n    \"files\": [\n      \"**/*\"\n    ],\n    \"afterSign\": \"notarize.js\",\n    \"win\": {\n      \"target\": \"nsis\"\n    },\n    \"mac\": {\n      \"target\": {\n        \"target\": \"default\",\n        \"arch\": [\n          \"x64\",\n          \"arm64\"\n        ]\n      },\n      \"category\": \"public.app-category.developer-tools\",\n      \"identity\": \"Sajal Jain (Z25C545DT5)\",\n      \"hardenedRuntime\": true,\n      \"gatekeeperAssess\": false,\n      \"icon\": \"assets/MyIcon.icns\"\n    },\n    \"linux\": {\n      \"target\": [\n        \"AppImage\",\n        \"deb\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/flowtest-electron/preload.js",
    "content": "const { ipcRenderer, contextBridge } = require('electron');\nconst path = require('path');\nconst { isMacOS } = require('./src/utils/filemanager/filesystem');\n\ncontextBridge.exposeInMainWorld('ipcRenderer', {\n  invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),\n  on: (channel, handler) => ipcRenderer.on(channel, (event, ...args) => handler(...args)),\n  join: (...args) => path.join(...args),\n  relative: (...args) => path.relative(...args),\n  dirname: (...args) => path.dirname(...args),\n  isMacOs: isMacOS,\n});\n"
  },
  {
    "path": "packages/flowtest-electron/src/ai/flowtestai.js",
    "content": "const BedrockClaudeGenerate = require('./models/bedrock_claude');\nconst GeminiGenerate = require('./models/gemini');\nconst OpenAIGenerate = require('./models/openai');\n\nclass FlowtestAI {\n  async generate(collection, user_instruction, model) {\n    if (model.name === 'OPENAI') {\n      const available_functions = await this.get_available_functions(collection);\n      const openai = new OpenAIGenerate();\n      const functions = await openai.filter_functions(available_functions, user_instruction, model.apiKey);\n      return await openai.process_user_instruction(functions, user_instruction, model.apiKey);\n    } else if (model.name === 'BEDROCK_CLAUDE') {\n      const available_functions = await this.get_available_functions(collection);\n      const bedrock_claude = new BedrockClaudeGenerate(model.apiKey);\n      const functions = await bedrock_claude.filter_functions(available_functions, user_instruction);\n      return await bedrock_claude.process_user_instruction(functions, user_instruction);\n    } else if (model.name === 'GEMINI') {\n      const available_functions = await this.get_available_functions(collection);\n      const gemini = new GeminiGenerate(model.apiKey);\n      const functions = await gemini.filter_functions(available_functions, user_instruction);\n      return await gemini.process_user_instruction(functions, user_instruction);\n    } else {\n      throw Error(`Model ${model.name} not supported`);\n    }\n  }\n\n  async get_available_functions(collection) {\n    let functions = [];\n    Object.entries(collection['paths']).map(([path, methods], index) => {\n      Object.entries(methods).map(([method, spec], index1) => {\n        const function_name = spec['operationId'];\n\n        const desc = spec['description'] || spec['summary'] || '';\n\n        let schema = { type: 'object', properties: {} };\n\n        let req_body = undefined;\n        if (spec['requestBody']) {\n          if (spec['requestBody']['content']) {\n            if (spec['requestBody']['content']['application/json']) {\n              if (spec['requestBody']['content']['application/json']['schema']) {\n                req_body = spec['requestBody']['content']['application/json']['schema'];\n              }\n            }\n          }\n        }\n\n        if (req_body != undefined) {\n          schema['properties']['requestBody'] = req_body;\n        }\n\n        const params = spec['parameters'] ? spec['parameters'] : [];\n        const param_properties = {};\n        if (params.length > 0) {\n          for (const param of params) {\n            if (param['schema']) {\n              param_properties[param['name']] = param['schema'];\n            }\n          }\n          schema['properties']['parameters'] = {\n            type: 'object',\n            properties: param_properties,\n          };\n        }\n\n        const f = {\n          type: 'function',\n          function: { name: function_name, description: desc, parameters: schema },\n        };\n\n        if (this.isCyclic(f)) {\n          functions.push({\n            type: 'function',\n            function: { name: function_name, description: desc, parameters: {} },\n          });\n        } else {\n          functions.push(f);\n        }\n      });\n    });\n\n    return functions;\n  }\n\n  isCyclic(obj) {\n    var keys = [];\n    var stack = [];\n    var stackSet = new Set();\n    var detected = false;\n\n    function detect(obj, key) {\n      if (obj && typeof obj != 'object') {\n        return false;\n      }\n\n      if (stackSet.has(obj)) {\n        // it's cyclic! Print the object and its locations.\n        var oldindex = stack.indexOf(obj);\n        var l1 = keys.join('.') + '.' + key;\n        var l2 = keys.slice(0, oldindex + 1).join('.');\n        //console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);\n        //console.log(obj);\n        detected = true;\n        return;\n      }\n\n      keys.push(key);\n      stack.push(obj);\n      stackSet.add(obj);\n      for (var k in obj) {\n        //dive on the object's children\n        if (Object.prototype.hasOwnProperty.call(obj, k)) {\n          detect(obj[k], k);\n        }\n      }\n\n      keys.pop();\n      stack.pop();\n      stackSet.delete(obj);\n      return;\n    }\n\n    detect(obj, 'obj', keys, stack, stackSet, detected);\n    return detected;\n  }\n}\n\nmodule.exports = FlowtestAI;\n"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/bedrock_claude.js",
    "content": "const { BedrockChat } = require('@langchain/community/chat_models/bedrock');\nconst { HumanMessage, SystemMessage, BaseMessage } = require('@langchain/core/messages');\nconst { BedrockEmbeddings } = require('@langchain/community/embeddings/bedrock');\nconst { MemoryVectorStore } = require('langchain/vectorstores/memory');\n\nclass BedrockClaudeGenerate {\n  constructor(creds) {\n    this.model = new BedrockChat({\n      model: 'anthropic.claude-3-sonnet-20240229-v1:0',\n      region: 'us-west-2',\n      // endpointUrl: \"custom.amazonaws.com\",\n      credentials: creds,\n      modelKwargs: {\n        anthropic_version: 'bedrock-2023-05-31',\n      },\n    });\n\n    this.embeddings = new BedrockEmbeddings({\n      region: 'us-west-2',\n      credentials: creds,\n      model: 'amazon.titan-embed-text-v2:0', // Default value\n    });\n  }\n\n  async filter_functions(functions, instruction) {\n    const documents = functions.map((f) => {\n      const { parameters, ...fDescription } = f.function;\n      return JSON.stringify(fDescription);\n    });\n\n    const vectorStore = await MemoryVectorStore.fromTexts(documents, [], this.embeddings);\n    // 128 (max no of functions accepted by openAI function calling)\n    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);\n    var selectedFunctions = [];\n    retrievedDocuments.forEach((document) => {\n      const pDocument = JSON.parse(document.pageContent);\n      const findF = functions.find(\n        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,\n      );\n      if (findF) {\n        selectedFunctions = selectedFunctions.concat(findF);\n      }\n    });\n    return selectedFunctions;\n  }\n\n  async process_user_instruction(functions, instruction) {\n    //console.log(functions.map((f) => f.function.name));\n    // Define the function call format\n    const fn = `{\"name\": \"function_name\"}`;\n\n    // Prepare the function string for the system prompt\n    const fnStr = functions.map((f) => JSON.stringify(f)).join('\\n');\n\n    // Define the system prompt\n    const systemPrompt = `\n        You are a helpful assistant with access to the following functions:\n\n        ${fnStr}\n\n        To use these functions respond with, only output function names, ignore arguments needed by those functions:\n\n        <multiplefunctions>\n            <functioncall> ${fn} </functioncall>\n            <functioncall> ${fn} </functioncall>\n            ...\n        </multiplefunctions>\n\n        Edge cases you must handle:\n        - If there are multiple functions that can fullfill user request, list them all.\n        - If there are no functions that match the user request, you will respond politely that you cannot help.\n        - If the user has not provided all information to execute the function call, choose the best possible set of values. Only, respond with the information requested and nothing else.\n        - If asked something that cannot be determined with the user's request details, respond that it is not possible to fulfill the request and explain why.\n    `;\n\n    // Prepare the messages for the language model\n    const messages = [new SystemMessage({ content: systemPrompt }), new HumanMessage({ content: instruction })];\n\n    // Invoke the language model and get the completion\n    const completion = await this.model.invoke(messages);\n    const content = completion.content.trim();\n\n    // Extract function calls from the completion\n    const extractedFunctions = this.extractFunctionCalls(content);\n\n    console.log(extractedFunctions);\n\n    return extractedFunctions;\n  }\n\n  extractFunctionCalls(completion) {\n    let content = typeof completion === 'string' ? completion : completion.content;\n\n    // Multiple functions lookup\n    const mfnPattern = /<multiplefunctions>(.*?)<\\/multiplefunctions>/s;\n    const mfnMatch = content.match(mfnPattern);\n\n    // Single function lookup\n    const singlePattern = /<functioncall>(.*?)<\\/functioncall>/s;\n    const singleMatch = content.match(singlePattern);\n\n    let functions = [];\n\n    if (!mfnMatch && !singleMatch) {\n      // No function calls found\n      return null;\n    } else if (mfnMatch) {\n      // Multiple function calls found\n      const multiplefn = mfnMatch[1];\n      const fnMatches = [...multiplefn.matchAll(/<functioncall>(.*?)<\\/functioncall>/gs)];\n      for (let fnMatch of fnMatches) {\n        const fnText = fnMatch[1].replace(/\\\\/g, '');\n        try {\n          functions.push(JSON.parse(fnText));\n        } catch {\n          // Ignore invalid JSON\n        }\n      }\n    } else {\n      // Single function call found\n      const fnText = singleMatch[1].replace(/\\\\/g, '');\n      try {\n        functions.push(JSON.parse(fnText));\n      } catch {\n        // Ignore invalid JSON\n      }\n    }\n    return functions;\n  }\n}\n\nmodule.exports = BedrockClaudeGenerate;\n"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/gemini.js",
    "content": "const { GoogleGenerativeAI } = require('@google/generative-ai');\nconst { GoogleGenerativeAIEmbeddings } = require('@langchain/google-genai');\nconst { TaskType } = require('@google/generative-ai');\nconst { MemoryVectorStore } = require('langchain/vectorstores/memory');\n\nclass GeminiGenerate {\n  constructor(apiKey) {\n    this.genAI = new GoogleGenerativeAI(apiKey);\n\n    this.embeddings = new GoogleGenerativeAIEmbeddings({\n      apiKey,\n      model: 'text-embedding-004', // 768 dimensions\n      taskType: TaskType.RETRIEVAL_DOCUMENT,\n      title: 'Document title',\n    });\n  }\n\n  async filter_functions(functions, instruction) {\n    const documents = functions.map((f) => {\n      const { parameters, ...fDescription } = f.function;\n      return JSON.stringify(fDescription);\n    });\n\n    const vectorStore = await MemoryVectorStore.fromTexts(documents, [], this.embeddings);\n\n    // 128 (max no of functions accepted by openAI function calling)\n    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);\n    var selectedFunctions = [];\n    retrievedDocuments.forEach((document) => {\n      const pDocument = JSON.parse(document.pageContent);\n      const findF = functions.find(\n        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,\n      );\n      if (findF) {\n        selectedFunctions = selectedFunctions.concat(findF);\n      }\n    });\n\n    return selectedFunctions;\n  }\n\n  async process_user_instruction(functions, instruction) {\n    //console.log(functions.map((f) => f.function.name));\n    // Define the function call format\n    const fn = `{\"name\": \"function_name\"}`;\n\n    // Prepare the function string for the system prompt\n    const fnStr = functions.map((f) => JSON.stringify(f)).join('\\n');\n\n    // Define the system prompt\n    const systemPrompt = `\n        You are a helpful assistant with access to the following functions:\n\n        ${fnStr}\n\n        To use these functions respond with, only output function names, ignore arguments needed by those functions:\n\n        <multiplefunctions>\n            <functioncall> ${fn} </functioncall>\n            <functioncall> ${fn} </functioncall>\n            ...\n        </multiplefunctions>\n\n        Edge cases you must handle:\n        - If there are multiple functions that can fullfill user request, list them all.\n        - If there are no functions that match the user request, you will respond politely that you cannot help.\n        - If the user has not provided all information to execute the function call, choose the best possible set of values. Only, respond with the information requested and nothing else.\n        - If asked something that cannot be determined with the user's request details, respond that it is not possible to fulfill the request and explain why.\n    `;\n\n    const model = this.genAI.getGenerativeModel({\n      model: 'gemini-1.5-pro-latest',\n      systemInstruction: {\n        role: 'system',\n        parts: [{ text: systemPrompt }],\n      },\n    });\n\n    // Prepare the messages for the language model\n\n    const request = {\n      contents: [{ role: 'user', parts: [{ text: instruction }] }],\n    };\n\n    // Invoke the language model and get the completion\n    const completion = await model.generateContent(request);\n\n    const content = completion.response.candidates[0].content.parts[0].text.trim();\n\n    // Extract function calls from the completion\n    const extractedFunctions = this.extractFunctionCalls(content);\n\n    return extractedFunctions;\n  }\n\n  extractFunctionCalls(completion) {\n    let content = typeof completion === 'string' ? completion : completion.content;\n\n    // Multiple functions lookup\n    const mfnPattern = /<multiplefunctions>(.*?)<\\/multiplefunctions>/s;\n    const mfnMatch = content.match(mfnPattern);\n\n    // Single function lookup\n    const singlePattern = /<functioncall>(.*?)<\\/functioncall>/s;\n    const singleMatch = content.match(singlePattern);\n\n    let functions = [];\n\n    if (!mfnMatch && !singleMatch) {\n      // No function calls found\n      return null;\n    } else if (mfnMatch) {\n      // Multiple function calls found\n      const multiplefn = mfnMatch[1];\n      const fnMatches = [...multiplefn.matchAll(/<functioncall>(.*?)<\\/functioncall>/gs)];\n      for (let fnMatch of fnMatches) {\n        const fnText = fnMatch[1].replace(/\\\\/g, '');\n        try {\n          functions.push(JSON.parse(fnText));\n        } catch {\n          // Ignore invalid JSON\n        }\n      }\n    } else {\n      // Single function call found\n      const fnText = singleMatch[1].replace(/\\\\/g, '');\n      try {\n        functions.push(JSON.parse(fnText));\n      } catch {\n        // Ignore invalid JSON\n      }\n    }\n    return functions;\n  }\n}\n\nmodule.exports = GeminiGenerate;\n"
  },
  {
    "path": "packages/flowtest-electron/src/ai/models/openai.js",
    "content": "const OpenAI = require('openai');\nconst { MemoryVectorStore } = require('langchain/vectorstores/memory');\nconst { OpenAIEmbeddings } = require('@langchain/openai');\n\nconst SYSTEM_MESSAGE = `You are a helpful assistant. \\ \n        Respond to the following prompt by using function_call and then summarize actions. \\ \n        If a user request is ambiguous, choose the best response possible.`;\n\n// Maximum number of function calls allowed to prevent infinite or lengthy loops\nconst MAX_CALLS = 10;\n\nclass OpenAIGenerate {\n  async filter_functions(functions, instruction, apiKey) {\n    const documents = functions.map((f) => {\n      const { parameters, ...fDescription } = f.function;\n      return JSON.stringify(fDescription);\n    });\n\n    const vectorStore = await MemoryVectorStore.fromTexts(\n      documents,\n      [],\n      new OpenAIEmbeddings({\n        openAIApiKey: apiKey,\n      }),\n    );\n\n    // 128 (max no of functions accepted by openAI function calling)\n    const retrievedDocuments = await vectorStore.similaritySearch(instruction, 10);\n    var selectedFunctions = [];\n    retrievedDocuments.forEach((document) => {\n      const pDocument = JSON.parse(document.pageContent);\n      const findF = functions.find(\n        (f) => f.function.name === pDocument.name && f.function.description === pDocument.description,\n      );\n      if (findF) {\n        selectedFunctions = selectedFunctions.concat(findF);\n      }\n    });\n\n    return selectedFunctions;\n  }\n\n  async get_openai_response(functions, messages, apiKey) {\n    const openai = new OpenAI({\n      apiKey,\n    });\n\n    return await openai.chat.completions.create({\n      model: 'gpt-4', //gpt-3.5-turbo-16k-0613\n      tools: functions,\n      tool_choice: 'auto', // \"auto\" means the model can pick between generating a message or calling a function.\n      temperature: 0,\n      messages: messages,\n    });\n  }\n\n  async process_user_instruction(functions, instruction, apiKey) {\n    //console.log(functions.map((f) => f.function.name));\n    let result = [];\n    let num_calls = 0;\n    const messages = [\n      { content: SYSTEM_MESSAGE, role: 'system' },\n      { content: instruction, role: 'user' },\n    ];\n\n    while (num_calls < MAX_CALLS) {\n      const response = await this.get_openai_response(functions, messages, apiKey);\n      const message = response['choices'][0]['message'];\n\n      if (message.tool_calls) {\n        messages.push(message);\n        message.tool_calls.map((tool_call) => {\n          console.log('Function call #: ', num_calls + 1);\n          console.log(JSON.stringify(tool_call));\n\n          // We'll simply add a message to simulate successful function call.\n          messages.push({\n            role: 'tool',\n            content: 'success',\n            tool_call_id: tool_call.id,\n          });\n          result.push(tool_call.function);\n\n          num_calls += 1;\n        });\n      } else {\n        console.log('Message: ');\n        console.log(message['content']);\n        break;\n      }\n    }\n\n    if (num_calls >= MAX_CALLS) {\n      console.log('Reached max chained function calls: ', MAX_CALLS);\n    }\n\n    return result;\n  }\n}\n\nmodule.exports = OpenAIGenerate;\n"
  },
  {
    "path": "packages/flowtest-electron/src/app/watcher.js",
    "content": "const chokidar = require('chokidar');\nconst path = require('path');\nconst dotenv = require('dotenv');\nconst { PATH_SEPARATOR, getSubdirectoriesFromRoot } = require('../utils/filemanager/filesystem');\nconst readFile = require('../utils/filemanager/readfile');\nconst { serialize } = require('../utils/flowparser/parser');\n\nclass Watcher {\n  constructor() {\n    this.watchers = {};\n  }\n\n  isFlowTestFile(pathname) {\n    if (!pathname || typeof pathname !== 'string') return false;\n    return ['flow'].some((ext) => pathname.toLowerCase().endsWith(`.${ext}`));\n  }\n\n  isEnvFile(pathname, collectionPath) {\n    if (!pathname || typeof pathname !== 'string') return false;\n    const dirname = path.dirname(pathname);\n    const envDirectory = path.join(collectionPath, 'environments');\n\n    return dirname === envDirectory && ['env'].some((ext) => pathname.toLowerCase().endsWith(`.${ext}`));\n  }\n\n  isDotEnvFile(pathname, collectionPath) {\n    const dirname = path.dirname(pathname);\n    const basename = path.basename(pathname);\n\n    return dirname === collectionPath && basename === '.env';\n  }\n\n  add(mainWindow, pathname, collectionId, watchPath) {\n    console.log(`[Watcher] File ${pathname} added`);\n    if (this.isFlowTestFile(pathname)) {\n      const content = readFile(pathname);\n      const flowData = serialize(JSON.parse(content));\n      const dirname = path.dirname(pathname);\n      const subDirectories = getSubdirectoriesFromRoot(watchPath, dirname);\n      const file = {\n        name: path.basename(pathname),\n        pathname: pathname,\n        subDirectories,\n        sep: PATH_SEPARATOR,\n        flowData,\n      };\n      mainWindow.webContents.send('main:create-flowtest', file, collectionId);\n    } else if (this.isEnvFile(pathname, watchPath)) {\n      try {\n        const variables = this.getEnvVariables(pathname);\n        const file = {\n          name: path.basename(pathname),\n          pathname: pathname,\n          variables,\n        };\n        mainWindow.webContents.send('main:addOrUpdate-environment', file, collectionId);\n      } catch (error) {\n        console.error(`Failed to add ${pathname} due to: ${error}`);\n      }\n    } else if (this.isDotEnvFile(pathname, watchPath)) {\n      try {\n        const variables = this.getEnvVariables(pathname);\n        mainWindow.webContents.send('main:addOrUpdate-dotEnvironment', variables, collectionId);\n      } catch (error) {\n        console.error(`Failed to add .env variables due to: ${error}`);\n      }\n    }\n  }\n\n  addDirectory(mainWindow, pathname, collectionId, watchPath) {\n    const envDirectory = path.join(watchPath, 'environments');\n\n    if (pathname === envDirectory) {\n      return;\n    }\n\n    if (pathname === watchPath) {\n      // we have already added collection object to store\n      return;\n    }\n\n    console.log(`[Watcher] Directory ${pathname} added`);\n    const directory = {\n      name: path.basename(pathname),\n      pathname: pathname,\n    };\n\n    const subDirsFromRoot = getSubdirectoriesFromRoot(watchPath, directory.pathname);\n    mainWindow.webContents.send('main:add-directory', directory, collectionId, subDirsFromRoot, PATH_SEPARATOR);\n  }\n\n  change(mainWindow, pathname, collectionId, watchPath) {\n    console.log(`[Watcher] file ${pathname} changed`);\n    if (this.isFlowTestFile(pathname)) {\n      const content = readFile(pathname);\n      const flowData = serialize(JSON.parse(content));\n      const file = {\n        name: path.basename(pathname),\n        pathname,\n        flowData,\n      };\n      mainWindow.webContents.send('main:update-flowtest', file, collectionId);\n    } else if (this.isEnvFile(pathname, watchPath)) {\n      try {\n        const variables = this.getEnvVariables(pathname);\n        const file = {\n          name: path.basename(pathname),\n          pathname: pathname,\n          variables,\n        };\n        mainWindow.webContents.send('main:addOrUpdate-environment', file, collectionId);\n      } catch (error) {\n        console.error(`Failed to save ${pathname} due to: ${error}`);\n      }\n    } else if (this.isDotEnvFile(pathname, watchPath)) {\n      try {\n        const variables = this.getEnvVariables(pathname);\n        mainWindow.webContents.send('main:addOrUpdate-dotEnvironment', variables, collectionId);\n      } catch (error) {\n        console.error(`Failed to add .env variables due to: ${error}`);\n      }\n    }\n  }\n\n  unlink(mainWindow, pathname, collectionId, watchPath) {\n    console.log(`[Watcher] File ${pathname} removed`);\n    if (this.isFlowTestFile(pathname)) {\n      const file = {\n        name: path.basename(pathname),\n        pathname: pathname,\n      };\n      mainWindow.webContents.send('main:delete-flowtest', file, collectionId);\n    } else if (this.isEnvFile(pathname, watchPath)) {\n      try {\n        const file = {\n          name: path.basename(pathname),\n          pathname: pathname,\n        };\n        mainWindow.webContents.send('main:delete-environment', file, collectionId);\n      } catch (error) {\n        console.error(`Failed to save ${pathname} due to: ${error}`);\n      }\n    }\n  }\n\n  unlinkDir(mainWindow, pathname, collectionId, watchPath) {\n    const envDirectory = path.join(watchPath, 'environments');\n\n    if (pathname === envDirectory) {\n      return;\n    }\n\n    console.log(`[Watcher] Directory ${pathname} removed`);\n    const directory = {\n      name: path.basename(pathname),\n      pathname: pathname,\n    };\n    mainWindow.webContents.send('main:delete-directory', directory, collectionId);\n  }\n\n  getEnvVariables(pathname) {\n    const content = readFile(pathname);\n    const buf = Buffer.from(content);\n    const parsed = dotenv.parse(buf);\n    return parsed;\n  }\n\n  addWatcher(mainWindow, watchPath, collectionId) {\n    if (!this.hasWatcher(watchPath)) {\n      console.log(`[Watcher] watcher added for path: ${watchPath} `);\n      if (this.watchers[watchPath]) {\n        this.watchers[watchPath].close();\n      }\n\n      setTimeout(() => {\n        const watcher = chokidar.watch(watchPath, {\n          ignoreInitial: false,\n          usePolling: watchPath.startsWith('\\\\\\\\') ? true : false,\n          ignored: (path) => ['node_modules', '.git'].some((s) => path.includes(s)),\n          persistent: true,\n          ignorePermissionErrors: true,\n          awaitWriteFinish: {\n            stabilityThreshold: 80,\n            pollInterval: 10,\n          },\n          depth: 20,\n        });\n\n        watcher\n          .on('add', (pathname) => this.add(mainWindow, pathname, collectionId, watchPath))\n          .on('addDir', (pathname) => this.addDirectory(mainWindow, pathname, collectionId, watchPath))\n          .on('change', (pathname) => this.change(mainWindow, pathname, collectionId, watchPath))\n          .on('unlink', (pathname) => this.unlink(mainWindow, pathname, collectionId, watchPath))\n          .on('unlinkDir', (pathname) => this.unlinkDir(mainWindow, pathname, collectionId, watchPath));\n\n        this.watchers[watchPath] = watcher;\n      }, 100);\n    }\n  }\n\n  hasWatcher(watchPath) {\n    return this.watchers[watchPath] != undefined ? true : false;\n  }\n\n  removeWatcher(watchPath) {\n    if (this.watchers[watchPath]) {\n      this.watchers[watchPath].close();\n      this.watchers[watchPath] = null;\n    }\n  }\n}\n\nmodule.exports = Watcher;\n"
  },
  {
    "path": "packages/flowtest-electron/src/ipc/axiosClient.js",
    "content": "// lib/axiosClient.ts\nconst axios = require('axios');\nconst axiosRetry = require('axios-retry').default;\n\nconst axiosClient = (baseUrl, accessId, accessKey) => {\n  const client = axios.create({\n    baseURL: `${baseUrl}/api`,\n    headers: {\n      'Content-Type': 'application/json',\n      'x-access-id': accessId,\n      'x-access-key': accessKey,\n    },\n  });\n\n  axiosRetry(client, {\n    retries: 3, // Number of retries\n    retryDelay: (retryCount) => {\n      return retryCount * 1000; // Time interval between retries (1000 ms = 1 second)\n    },\n    retryCondition: (error) => {\n      // Retry on network errors or rate limit errors or 5xx server errors\n      return error.response?.status === 500 || error.response?.status === 429 || error.code === 'ECONNABORTED';\n    },\n  });\n\n  return client;\n};\n\nmodule.exports = {\n  axiosClient,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/ipc/collection.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst axios = require('axios');\nconst { ipcMain, shell, dialog, app } = require('electron');\nconst SwaggerParser = require('@apidevtools/swagger-parser');\nconst JsonRefs = require('json-refs');\nconst createDirectory = require('../utils/filemanager/createdirectory');\nconst deleteDirectory = require('../utils/filemanager/deletedirectory');\nconst uuidv4 = require('uuid').v4;\nconst Collections = require('../store/collection');\nconst { parseOpenAPISpec } = require('../utils/collection');\nconst { isDirectory, pathExists } = require('../utils/filemanager/filesystem');\nconst createFile = require('../utils/filemanager/createfile');\nconst updateFile = require('../utils/filemanager/updatefile');\nconst deleteFile = require('../utils/filemanager/deletefile');\nconst readFile = require('../utils/filemanager/readfile');\nconst FlowtestAI = require('../ai/flowtestai');\nconst { stringify, parse } = require('flatted');\nconst { deserialize, serialize } = require('../utils/flowparser/parser');\nconst { axiosClient } = require('./axiosClient');\nconst FormData = require('form-data');\nconst { extend, cloneDeep } = require('lodash');\n\nconst collectionStore = new Collections();\nconst flowTestAI = new FlowtestAI();\n\nconst timeout = 60000;\n\nconst newAbortSignal = () => {\n  const abortController = new AbortController();\n  setTimeout(() => abortController.abort(), timeout || 0);\n\n  return abortController.signal;\n};\n\n/** web platform: blob. */\nconst convertBase64ToBlob = async (base64) => {\n  const response = await fetch(base64);\n  const blob = await response.blob();\n  return blob;\n};\n\nconst registerRendererEventHandlers = (mainWindow, watcher) => {\n  ipcMain.handle('renderer:open-directory-selection-dialog', async (event, arg) => {\n    try {\n      const result = await dialog.showOpenDialog(mainWindow, {\n        properties: ['openDirectory'],\n      });\n      return result.filePaths[0];\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:browser-window-ready', async (event) => {\n    const savedCollections = collectionStore.getAll();\n\n    for (let i = 0; i < savedCollections.length; i++) {\n      if (isDirectory(savedCollections[i].pathname)) {\n        mainWindow.webContents.send(\n          'main:collection-created',\n          savedCollections[i].id,\n          path.basename(savedCollections[i].pathname),\n          savedCollections[i].pathname,\n          savedCollections[i].nodes,\n        );\n\n        watcher.addWatcher(mainWindow, savedCollections[i].pathname, savedCollections[i].id);\n      } else {\n        collectionStore.remove(savedCollections[i]);\n      }\n    }\n  });\n\n  ipcMain.handle('renderer:create-collection', async (event, openAPISpecFilePath, collectionFolderPath) => {\n    try {\n      const spec = fs.readFileSync(openAPISpecFilePath, 'utf8');\n      // async/await syntax\n      let api = await SwaggerParser.validate(openAPISpecFilePath);\n      // console.log(\"API name: %s, Version: %s\", api.info.title, api.info.version);\n\n      // resolve references in openapi spec\n      const resolvedSpec = await JsonRefs.resolveRefs(api);\n      const parsedNodes = parseOpenAPISpec(resolvedSpec.resolved);\n\n      const id = uuidv4();\n      const collectionName = api.info.title;\n      const pathname = path.join(collectionFolderPath, collectionName);\n\n      const newCollection = {\n        id: id,\n        name: collectionName,\n        pathname: pathname,\n        openapi_spec: stringify(resolvedSpec.resolved),\n        nodes: parsedNodes,\n      };\n\n      const result = createDirectory(newCollection.name, collectionFolderPath);\n      console.log(`Created directory: ${result}`);\n      createDirectory('environments', pathname);\n\n      mainWindow.webContents.send('main:collection-created', id, path.basename(pathname), pathname, parsedNodes);\n\n      watcher.addWatcher(mainWindow, pathname, id);\n      collectionStore.add(newCollection);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:open-collection', async (event, openAPISpecFilePath, collectionFolderPath) => {\n    try {\n      if (isDirectory(collectionFolderPath)) {\n        // async/await syntax\n        const api = await SwaggerParser.validate(openAPISpecFilePath);\n        // console.log(\"API name: %s, Version: %s\", api.info.title, api.info.version);\n\n        // resolve references in openapi spec\n        const resolvedSpec = await JsonRefs.resolveRefs(api);\n        const parsedNodes = parseOpenAPISpec(resolvedSpec.resolved);\n\n        const id = uuidv4();\n        const collectionName = api.info.title;\n\n        const newCollection = {\n          id: id,\n          name: collectionName,\n          pathname: collectionFolderPath,\n          openapi_spec: stringify(resolvedSpec.resolved),\n          nodes: parsedNodes,\n        };\n\n        mainWindow.webContents.send('main:collection-created', id, collectionName, collectionFolderPath, parsedNodes);\n\n        watcher.addWatcher(mainWindow, collectionFolderPath, id);\n        collectionStore.add(newCollection);\n      } else {\n        return Promise.reject(new Error(`Directory: ${collectionFolderPath} does not exist`));\n      }\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:delete-collection', async (event, collection) => {\n    try {\n      // deleteDirectory(collection.pathname);\n      // console.log(`Deleted directory: ${collection.pathname}`);\n\n      mainWindow.webContents.send('main:collection-deleted', collection.id);\n\n      watcher.removeWatcher(collection.pathname);\n      collectionStore.remove(collection);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:create-folder', async (event, name, path) => {\n    try {\n      const result = createDirectory(name, path);\n      console.log(`Created directory: ${result}`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:delete-folder', async (event, path) => {\n    try {\n      const result = deleteDirectory(path);\n      console.log(`Deleted directory: ${path}`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:create-environment', async (event, collectionPath, name) => {\n    try {\n      const envDir = path.join(collectionPath, 'environments');\n      if (!isDirectory(envDir)) {\n        createDirectory('environments', collectionPath);\n      }\n      const result = createFile(`${name}.env`, envDir, '');\n      console.log(`Created file: ${name}.env`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:update-environment', async (event, collectionPath, name, variables) => {\n    try {\n      const env = Object.entries(variables)\n        .map(([key, value]) => `${key}: \"${value}\"`)\n        .join('\\n');\n      const envDir = path.join(collectionPath, 'environments');\n      updateFile(path.join(envDir, name), env);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:delete-environment', async (event, collectionPath, name) => {\n    try {\n      const envDir = path.join(collectionPath, 'environments');\n      deleteFile(path.join(envDir, name));\n      console.log(`Delete file: ${name}`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:create-dotenv', async (event, collectionPath, content) => {\n    try {\n      createFile('.env', collectionPath, content || '');\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:addOrUpdate-dotEnvironment', async (event, collectionPath, variables) => {\n    try {\n      const pathname = path.join(collectionPath, '.env');\n      // variables should be of format `k1=v1\\nk2=v2`;\n\n      updateFile(pathname, variables);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:create-flowtest', async (event, name, path, flowData) => {\n    try {\n      if (isDirectory(path)) {\n        const textData = deserialize(flowData);\n        createFile(`${name}.flow`, path, JSON.stringify(textData, null, 4));\n        console.log(`Created file: ${name}.flow`);\n      }\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:clone-flowtest', async (event, name, flowtestPath) => {\n    try {\n      const content = readFile(flowtestPath);\n      createFile(`${name}.flow`, path.dirname(flowtestPath), content);\n      console.log(`Cloned file: ${name}.flow`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:read-flowtest', async (event, pathname, collectionId) => {\n    try {\n      const content = readFile(pathname);\n      const flowData = serialize(JSON.parse(content));\n      mainWindow.webContents.send('main:read-flowtest', pathname, collectionId, flowData);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:read-flowtest-sync', (event, pathname) => {\n    const content = readFile(pathname);\n    const flowData = serialize(JSON.parse(content));\n    return flowData;\n  });\n\n  ipcMain.handle('renderer:update-flowtest', async (event, pathname, flowData) => {\n    try {\n      const textData = deserialize(flowData);\n      updateFile(pathname, JSON.stringify(textData, null, 4));\n      console.log(`Updated file: ${pathname}`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:delete-flowtest', async (event, pathname) => {\n    try {\n      deleteFile(pathname);\n      console.log(`Delete file: ${pathname}`);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:run-http-request', async (event, request, collectionPath) => {\n    let requestSent;\n    try {\n      if (request.headers['content-type'] === 'multipart/form-data') {\n        const formData = new FormData();\n        const params = request.data;\n        await params.map(async (param, index) => {\n          if (param.type === 'text') {\n            formData.append(param.key, param.value);\n          }\n\n          if (param.type === 'file') {\n            let trimmedFilePath = param.value.trim();\n\n            if (!path.isAbsolute(trimmedFilePath)) {\n              trimmedFilePath = path.join(collectionPath, trimmedFilePath);\n            }\n\n            formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));\n          }\n        });\n\n        request.data = formData;\n        extend(request.headers, formData.getHeaders());\n      }\n\n      requestSent = {\n        url: request.url,\n        method: request.method,\n        headers: request.headers,\n        // form data obj gets serialized here so that it can be sent over wire\n        // otherwise ipc communication errors out\n        data: request.data ? JSON.parse(JSON.stringify(request.data)) : request.data,\n      };\n\n      const result = await axios({\n        ...request,\n        signal: newAbortSignal(),\n      });\n\n      return {\n        request: requestSent,\n        response: {\n          status: result.status,\n          statusText: result.statusText,\n          data: result.data,\n          headers: result.headers,\n        },\n      };\n    } catch (error) {\n      if (error?.response) {\n        return {\n          request: requestSent,\n          response: {\n            error: {\n              status: error.response.status,\n              statusText: error.response.statusText,\n              data: error.response.data,\n              headers: error.response.headers,\n            },\n          },\n        };\n      } else {\n        return {\n          request: requestSent,\n          response: {\n            error: {\n              status: '',\n              statusText: '',\n              data: `An error occurred while running the request : ${error?.message}`,\n            },\n          },\n        };\n      }\n    }\n  });\n\n  ipcMain.handle('renderer:generate-nodes-ai', async (event, instruction, collectionId, model) => {\n    try {\n      const collection = collectionStore.getAll().find((c) => c.id === collectionId);\n      if (collection) {\n        return await flowTestAI.generate(parse(collection.openapi_spec), instruction, model);\n      } else {\n        return Promise.reject(new Error('Collection not found'));\n      }\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:upload-logs', async (event, name, config, status, time, logs) => {\n    function bytesToBase64(bytes) {\n      const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');\n      return btoa(binString);\n    }\n\n    try {\n      const data = {\n        scan_metadata: {\n          version: 1,\n          name,\n          status,\n          time,\n        },\n        scan: bytesToBase64(new TextEncoder().encode(JSON.stringify(logs))),\n      };\n      try {\n        const response = await axiosClient(config.hostUrl, config.accessId, config.accessKey).post('/upload', data);\n        return {\n          upload: 'success',\n          url: `${config.hostUrl}/scan/${response.data.data[0].id}`,\n        };\n      } catch (error) {\n        if (error?.response) {\n          if (error.response?.status >= 400 && error.response?.status < 500) {\n            return {\n              upload: 'fail',\n              message: 'Unable to upload flow scan',\n              reason: `${JSON.stringify(error.response?.data)}`,\n            };\n          }\n\n          if (error.response?.status === 500) {\n            return {\n              upload: 'fail',\n              message: 'Unable to upload flow scan',\n              reason: 'Internal Server Error',\n            };\n          }\n        }\n        return {\n          upload: 'fail',\n          message: 'Unable to upload flow scan',\n        };\n      }\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n};\n\nmodule.exports = registerRendererEventHandlers;\n"
  },
  {
    "path": "packages/flowtest-electron/src/ipc/settings.js",
    "content": "const { ipcMain, shell, dialog, app } = require('electron');\nconst Settings = require('../store/settings');\n\nconst settingsStore = new Settings();\n\nconst registerSettingsEventHandlers = (mainWindow) => {\n  ipcMain.handle('renderer:settings-window-ready', async (event) => {\n    const savedSettings = settingsStore.getAll();\n\n    mainWindow.webContents.send('main:saved-settings', savedSettings);\n  });\n\n  ipcMain.handle('renderer:add-logsyncconfig', async (event, config) => {\n    try {\n      settingsStore.addLogSyncConfig(config.enabled, config.hostUrl, config.accessId, config.accessKey);\n      const savedSettings = settingsStore.getAll();\n\n      mainWindow.webContents.send('main:saved-settings', savedSettings);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n\n  ipcMain.handle('renderer:add-genAIUsageDisclaimer', async (event, accepted) => {\n    try {\n      settingsStore.addGenAIUsageDisclaimer(accepted);\n      const savedSettings = settingsStore.getAll();\n\n      mainWindow.webContents.send('main:saved-settings', savedSettings);\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  });\n};\n\nmodule.exports = registerSettingsEventHandlers;\n"
  },
  {
    "path": "packages/flowtest-electron/src/store/collection.js",
    "content": "const Store = require('electron-store');\nconst { isDirectory } = require('../utils/filemanager/filesystem');\n\nclass Collections {\n  constructor() {\n    this.store = new Store();\n  }\n\n  add(collection) {\n    const collections = this.store.get('collections') || [];\n\n    if (isDirectory(collection.pathname)) {\n      if (!collections.find((c) => c.pathname === collection.pathname)) {\n        collections.push(collection);\n        this.store.set('collections', collections);\n      }\n    }\n  }\n\n  remove(collection) {\n    const collections = this.store.get('collections') || [];\n\n    if (collections.find((c) => c.id === collection.id)) {\n      this.store.set(\n        'collections',\n        collections.filter((c) => c.pathname !== collection.pathname),\n      );\n    }\n  }\n\n  getAll() {\n    return this.store.get('collections') || [];\n  }\n\n  removeAll() {\n    return this.store.set('collections', []);\n  }\n}\n\nmodule.exports = Collections;\n"
  },
  {
    "path": "packages/flowtest-electron/src/store/settings.js",
    "content": "const Store = require('electron-store');\n\nclass Settings {\n  constructor() {\n    this.store = new Store();\n  }\n\n  addLogSyncConfig(enabled, hostUrl, accessId, accessKey) {\n    this.store.set('logSyncConfig', { enabled, hostUrl, accessId, accessKey });\n  }\n\n  addGenAIUsageDisclaimer(accepted) {\n    this.store.set('genAIUsageDisclaimer', accepted);\n  }\n\n  getAll() {\n    return {\n      logSyncConfig: this.store.get('logSyncConfig') || {},\n      genAIUsageDisclaimer: this.store.get('genAIUsageDisclaimer') || false,\n    };\n  }\n\n  clearAll() {\n    this.store.set('logSyncConfig', {});\n    this.store.set('genAIUsageDisclaimer', false);\n  }\n}\n\nmodule.exports = Settings;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/collection.js",
    "content": "const { generateRequestBodyExample } = require('./generate-request-body');\nconst {\n  generateQueryParamsExample,\n  generateParameterExample,\n  generatePathParamsExample,\n} = require('./generate-request-parameters');\n\nconst computeUrl = (baseUrl, path) => {\n  if (baseUrl.charAt(baseUrl.length - 1) === '/' && path.charAt(0) === '/') {\n    return baseUrl + path.substring(1, path.length);\n  } else if (baseUrl.charAt(baseUrl.length - 1) !== '/' && path.charAt(0) !== '/') {\n    return baseUrl + '/' + path;\n  } else {\n    return baseUrl + path;\n  }\n};\n\nconst replaceSingleToDoubleCurlyBraces = (str) => {\n  // Replace opening curly braces\n  str = str.replace(/{/g, '{{');\n  // Replace closing curly braces\n  str = str.replace(/}/g, '}}');\n  return str;\n};\n\nconst parseOpenAPISpec = (collection) => {\n  let parsedNodes = [];\n  try {\n    // servers is array,, figure case where there can be multiple servers\n    const baseUrl = collection['servers'].length > 1 ? '{baseUrl}' : collection['servers'][0]['url'];\n    Object.entries(collection['paths']).map(([path, operations], _) => {\n      const commonParameters = Object.prototype.hasOwnProperty.call(operations, 'parameters')\n        ? operations['parameters']\n        : [];\n      const { parameters, ...operationsFiltered } = operations;\n      Object.entries(operationsFiltered).map(([requestType, request], _) => {\n        const summary = request['summary'];\n        const operationId = request['operationId'];\n        const tags = request['tags'];\n        var url = replaceSingleToDoubleCurlyBraces(computeUrl(baseUrl, path));\n        var variables = {};\n        var requestBody = {};\n        const pathParameters = [];\n        const queryParameters = [];\n\n        const requestParameters = commonParameters.map((obj) => {\n          if (request['parameters']) {\n            // Find the object in the second array that has the same id as the current object\n            const objFromArr2 = request['parameters'].find((o) => o.name === obj.name && o.in === obj.in);\n            // If found, merge the two objects, otherwise return the original object\n            return objFromArr2 ? { ...obj, ...objFromArr2 } : obj;\n          } else {\n            return obj;\n          }\n        });\n\n        if (request['parameters']) {\n          // Add any objects from the second array that do not exist in the first array\n          request['parameters'].forEach((obj) => {\n            if (!commonParameters.some((o) => o.name === obj.name && o.in === obj.in)) {\n              requestParameters.push(obj);\n            }\n          });\n        }\n\n        if (requestParameters.length > 0) {\n          let firstQueryParam = true;\n          requestParameters.map((value, _) => {\n            if (value['in'] === 'query') {\n              if (firstQueryParam) {\n                url = url.concat(`?${value['name']}={{${value['name']}}}`);\n                firstQueryParam = false;\n              } else {\n                url = url.concat(`&${value['name']}={{${value['name']}}}`);\n              }\n              queryParameters.push(value);\n            }\n\n            if (value['in'] === 'path') {\n              pathParameters.push(value);\n            }\n          });\n        }\n\n        if (queryParameters.length > 0) {\n          const res = generateQueryParamsExample(queryParameters);\n          Array.from(res.entries()).map(([key, value], index) => {\n            variables[key] = {\n              type: typeof value,\n              value,\n            };\n          });\n        }\n\n        if (pathParameters.length > 0) {\n          const res = generatePathParamsExample(pathParameters);\n          Array.from(res.entries()).map(([key, value], index) => {\n            variables[key] = {\n              type: typeof value,\n              value,\n            };\n          });\n        }\n\n        if (request['requestBody']) {\n          if (request['requestBody']['content']['application/json']) {\n            requestBody = {\n              type: 'raw-json',\n              body: JSON.stringify(\n                generateRequestBodyExample(request['requestBody']['content']['application/json']['schema']),\n              ),\n            };\n          }\n\n          if (request['requestBody']['content']['multipart/form-data']) {\n            requestBody = {\n              type: 'form-data',\n              body: [],\n            };\n          }\n        }\n\n        const finalNode = {\n          url: url,\n          description: summary,\n          operationId: operationId,\n          requestType: requestType.toUpperCase(),\n          tags: tags,\n          requestBody,\n          preReqVars: variables,\n        };\n\n        parsedNodes.push(finalNode);\n      });\n    });\n  } catch (err) {\n    console.error(err);\n  }\n  return parsedNodes;\n};\n\nmodule.exports = {\n  parseOpenAPISpec,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/collection.test.js",
    "content": "const { generateRequestBodyExample } = require('./generate-request-body.js');\nconst { generatePathParamsExample, generateQueryParamsExample } = require('./generate-request-parameters.js');\n\ndescribe('collection parser', () => {\n  it('should generate request body example', () => {\n    console.log(JSON.stringify(generateRequestBodyExample(userSchema), null, 2));\n    console.log(JSON.stringify(generateRequestBodyExample(productSchema), null, 2));\n    console.log(JSON.stringify(generateRequestBodyExample(complexSchema), null, 2));\n  });\n\n  it('should generate request parameters example', () => {\n    console.log('Path Parameters Example:', generatePathParamsExample(pathParameters).toString());\n    console.log('Query Parameters Example:', generateQueryParamsExample(queryParameters).toString());\n  });\n});\n\nconst userSchema = {\n  type: 'object',\n  properties: {\n    id: {\n      type: 'integer',\n      format: 'int64',\n      example: 1,\n      minimum: 1,\n    },\n    name: {\n      type: 'string',\n      example: 'John Doe',\n      minLength: 3,\n      maxLength: 20,\n    },\n    email: {\n      type: 'string',\n      format: 'email',\n      example: 'john.doe@example.com',\n    },\n    birthdate: {\n      type: 'string',\n      format: 'date',\n      example: '1990-01-01',\n    },\n    website: {\n      type: 'string',\n      format: 'uri',\n      example: 'https://johndoe.com',\n    },\n    role: {\n      type: 'string',\n      enum: ['admin', 'user', 'guest'],\n      example: 'user',\n    },\n    username: {\n      type: 'string',\n      pattern: '^[a-zA-Z0-9]{3,}$',\n      example: 'user123',\n    },\n    interests: {\n      type: 'array',\n      items: {\n        type: 'string',\n      },\n      example: ['coding', 'reading'],\n    },\n  },\n};\n\nconst productSchema = {\n  type: 'object',\n  properties: {\n    id: {\n      type: 'integer',\n      format: 'int32',\n      example: 101,\n      minimum: 1,\n      maximum: 1000,\n    },\n    name: {\n      type: 'string',\n      example: 'Sample Product',\n      minLength: 3,\n    },\n    price: {\n      type: 'number',\n      format: 'double',\n      example: 19.99,\n      minimum: 0,\n      maximum: 1000,\n    },\n    tags: {\n      type: 'array',\n      items: {\n        type: 'string',\n        example: 'tag1',\n      },\n    },\n    status: {\n      type: 'string',\n      enum: ['available', 'out of stock', 'discontinued'],\n      example: 'available',\n    },\n    releaseDate: {\n      type: 'string',\n      format: 'date-time',\n      example: '2023-01-01T00:00:00Z',\n    },\n  },\n};\n\nconst complexSchema = {\n  type: 'object',\n  properties: {\n    name: {\n      type: 'string',\n      example: 'Complex Example',\n    },\n    detail: {\n      oneOf: [\n        { type: 'string', example: 'OneOf String' },\n        { type: 'integer', example: 42 },\n      ],\n    },\n    options: {\n      anyOf: [\n        { type: 'boolean', example: true },\n        { type: 'string', example: 'AnyOf String' },\n      ],\n    },\n    allDetails: {\n      allOf: [\n        {\n          type: 'object',\n          properties: {\n            part1: {\n              type: 'string',\n              example: 'Part 1',\n            },\n          },\n        },\n        {\n          type: 'object',\n          properties: {\n            part2: {\n              type: 'number',\n              example: 123.45,\n            },\n          },\n        },\n      ],\n    },\n  },\n};\n\n// Example usage:\n\nconst pathParameters = [\n  {\n    name: 'userId',\n    in: 'path',\n    required: true,\n    schema: {\n      type: 'integer',\n      format: 'int64',\n      example: 123,\n      minimum: 1,\n    },\n  },\n  {\n    name: 'username',\n    in: 'path',\n    required: true,\n    schema: {\n      type: 'string',\n      minLength: 3,\n      maxLength: 20,\n      example: 'john_doe',\n    },\n  },\n];\n\nconst queryParameters = [\n  {\n    name: 'page',\n    in: 'query',\n    schema: {\n      type: 'integer',\n      example: 1,\n      minimum: 1,\n    },\n  },\n  {\n    name: 'limit',\n    in: 'query',\n    schema: {\n      type: 'integer',\n      example: 10,\n      minimum: 1,\n      maximum: 100,\n    },\n  },\n  {\n    name: 'sort',\n    in: 'query',\n    schema: {\n      type: 'string',\n      enum: ['asc', 'desc'],\n      example: 'asc',\n    },\n  },\n  {\n    name: 'filter',\n    in: 'query',\n    schema: {\n      type: 'array',\n      items: {\n        type: 'string',\n        example: 'status:active',\n      },\n    },\n  },\n  {\n    name: 'search',\n    in: 'query',\n    schema: {\n      type: 'string',\n      example: 'example',\n    },\n  },\n];\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/createdirectory.js",
    "content": "const fs = require('fs');\nconst { isDirectory, pathExists } = require('./filesystem');\nconst path = require('path');\n\nconst createDirectory = (name, basePath) => {\n  // now validate the name and path\n  if (!name) {\n    throw new Error('Directory name is required');\n  }\n\n  if (!basePath) {\n    throw new Error('Directory path is required');\n  }\n\n  // check if the directory exists\n  if (!isDirectory(basePath)) {\n    throw new Error('Path is not a directory');\n  }\n\n  // check if the directory already exists\n  const directoryPath = path.join(basePath, name);\n\n  if (isDirectory(directoryPath)) {\n    throw new Error('The directory already exists');\n  }\n\n  // now create the directory\n  return fs.mkdirSync(directoryPath, {\n    mode: 0o777,\n    recursive: true,\n  });\n};\n\nmodule.exports = createDirectory;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/createfile.js",
    "content": "const fs = require('fs');\nconst dpath = require('path');\nconst { isDirectory, pathExists } = require('./filesystem');\n\nconst createFile = (name, path, content) => {\n  // now validate the name and path\n  if (!name) {\n    throw new Error('File name is required');\n  }\n\n  if (!path) {\n    throw new Error('Directory path is required');\n  }\n\n  // check if the directory exists\n  if (!isDirectory(path)) {\n    throw new Error('Path is not a directory');\n  }\n\n  // check if the file already exists\n  const filePath = dpath.join(path, name);\n\n  if (pathExists(filePath)) {\n    throw new Error(`File already exists ${filePath}`);\n  }\n\n  // now create the file\n  return fs.writeFileSync(filePath, String(content), 'utf8');\n};\n\nmodule.exports = createFile;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/deletedirectory.js",
    "content": "const fs = require('fs');\nconst { isDirectory } = require('./filesystem');\n\nconst deleteDirectory = (path) => {\n  if (!path) {\n    throw new Error('Directory path is required');\n  }\n\n  // check if the directory exists\n  if (!isDirectory(path)) {\n    throw new Error('Path is not a directory');\n  }\n\n  // now delete the directory\n  return fs.rmSync(path, { recursive: true, force: true });\n};\n\nmodule.exports = deleteDirectory;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/deletefile.js",
    "content": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst deleteFile = (path) => {\n  if (!path) {\n    throw new Error('File path is required');\n  }\n\n  // check if file exists\n  if (!pathExists(path)) {\n    throw new Error('File does not exist');\n  }\n\n  // now delete the file\n  return fs.rmSync(path, { recursive: true, force: true });\n};\n\nmodule.exports = deleteFile;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/filesystem.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\n/**\n * Determine if the given path is directory\n */\nconst isDirectory = (path) => {\n  try {\n    return fs.existsSync(path) && fs.lstatSync(path).isDirectory();\n  } catch (e) {\n    // lstatSync throws an error if path doesn't exist\n    return false;\n  }\n};\n\n/**\n * Determine if the given path exists\n */\nconst pathExists = (path) => {\n  try {\n    fs.accessSync(path);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n\nconst getSubdirectoriesFromRoot = (rootPath, pathname) => {\n  // convert to unix style path\n  pathname = slash(pathname);\n  rootPath = slash(rootPath);\n\n  const relativePath = path.relative(rootPath, pathname);\n  return relativePath ? relativePath.split(path.sep) : [];\n};\n\nconst getDirectoryName = (pathname) => {\n  // convert to unix style path\n  pathname = slash(pathname);\n\n  return path.dirname(pathname);\n};\n\nconst isWindowsOS = () => {\n  return process.platform === 'win32';\n};\n\nconst isMacOS = () => {\n  return process.platform === 'darwin';\n};\n\nconst PATH_SEPARATOR = isWindowsOS() ? '\\\\' : '/';\n\nconst slash = (path) => {\n  const isExtendedLengthPath = /^\\\\\\\\\\?\\\\/.test(path);\n\n  if (isExtendedLengthPath) {\n    return path;\n  }\n\n  return path.replace(/\\\\/g, '/');\n};\n\nmodule.exports = {\n  isDirectory,\n  pathExists,\n  getSubdirectoriesFromRoot,\n  getDirectoryName,\n  isMacOS,\n  PATH_SEPARATOR,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/readfile.js",
    "content": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst readFile = (path) => {\n  if (!path) {\n    throw new Error('File path is required');\n  }\n\n  // check if file exists\n  if (!pathExists(path)) {\n    throw new Error('File does not exist');\n  }\n\n  // now delete the file\n  return fs.readFileSync(path, 'utf8');\n};\n\nmodule.exports = readFile;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/filemanager/updatefile.js",
    "content": "const fs = require('fs');\nconst { pathExists } = require('./filesystem');\n\nconst upadateFile = (path, content) => {\n  if (!path) {\n    throw new Error('File path is required');\n  }\n\n  // check if file exists\n  if (!pathExists(path)) {\n    throw new Error('File does not exist');\n  }\n\n  // now update the file\n  return fs.writeFileSync(path, String(content), 'utf8');\n};\n\nmodule.exports = upadateFile;\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/AssertNode.js",
    "content": "const { Node } = require('./Node');\n\nclass AssertNode extends Node {\n  constructor() {\n    super('assertNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  AssertNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/AuthNode.js",
    "content": "const { Node } = require('./Node');\n\nclass AuthNode extends Node {\n  constructor() {\n    super('authNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  AuthNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/DelayNode.js",
    "content": "const { Node } = require('./Node');\n\nclass DelayNode extends Node {\n  constructor() {\n    super('delayNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  DelayNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/NestedFlowNode.js",
    "content": "const { Node } = require('./Node');\n\nclass NestedFlowNode extends Node {\n  constructor() {\n    super('flowNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  NestedFlowNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/Node.js",
    "content": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  serialize(id, data, metadata) {\n    throw new Error('Serialize method must be implemented by subclasses');\n  }\n\n  deserialize(node) {\n    throw new Error('Deserialize method must be implemented by subclasses');\n  }\n}\n\nmodule.exports = {\n  Node,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/OutputNode.js",
    "content": "const { Node } = require('./Node');\n\nclass OutputNode extends Node {\n  constructor() {\n    super('outputNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const { ['output']: _, ...data } = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  OutputNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/RequestNode.js",
    "content": "const { Node } = require('./Node');\n\nclass RequestNode extends Node {\n  constructor() {\n    super('requestNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  RequestNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/SetVarNode.js",
    "content": "const { Node } = require('./Node');\n\nclass SetVarNode extends Node {\n  constructor() {\n    super('setVarNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      type: this.type,\n      data,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    const data = node.data;\n    delete node.id;\n    delete node.data;\n    const metadata = node;\n\n    return {\n      id,\n      data,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  SetVarNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/StartNode.js",
    "content": "const { Node } = require('./Node');\n\nclass StartNode extends Node {\n  constructor() {\n    super('startNode');\n  }\n\n  serialize(id, data, metadata) {\n    return {\n      id,\n      ...metadata,\n    };\n  }\n\n  deserialize(node) {\n    const id = node.id;\n    delete node.id;\n    const metadata = node;\n\n    return {\n      id,\n      metadata,\n    };\n  }\n}\n\nmodule.exports = {\n  StartNode,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/flowparser/parser.js",
    "content": "const { cloneDeep } = require('lodash');\nconst { AuthNode } = require('./AuthNode');\nconst { NestedFlowNode } = require('./NestedFlowNode');\nconst { DelayNode } = require('./DelayNode');\nconst { AssertNode } = require('./AssertNode');\nconst { OutputNode } = require('./OutputNode');\nconst { RequestNode } = require('./RequestNode');\nconst { StartNode } = require('./StartNode');\nconst { SetVarNode } = require('./SetVarNode');\n\nconst VERSION = 1;\n\nconst deserialize = (flowData) => {\n  // we don't want to modify original object\n  const flowDataCopy = cloneDeep(flowData);\n\n  const textData = {};\n  textData.version = VERSION;\n  textData.graph = {};\n\n  if (flowData) {\n    if (flowData.nodes) {\n      const nodes = flowDataCopy.nodes;\n      textData.graph.data = {};\n      textData.graph.data.nodes = {};\n      textData.graph.metadata = {};\n      textData.graph.metadata.nodes = {};\n\n      nodes.forEach((node) => {\n        if (node.type === 'startNode') {\n          const sNode = new StartNode();\n          const result = sNode.deserialize(node);\n\n          textData.graph.data.nodes[result.id] = {\n            type: 'startNode',\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'startNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'authNode') {\n          const aNode = new AuthNode();\n          const result = aNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'authNode',\n            auth: result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'authNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'requestNode') {\n          const rNode = new RequestNode();\n          const result = rNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'requestNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'requestNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'outputNode') {\n          const oNode = new OutputNode();\n          const result = oNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'outputNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'outputNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'delayNode') {\n          const dNode = new DelayNode();\n          const result = dNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'delayNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'delayNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'assertNode') {\n          const eNode = new AssertNode();\n          const result = eNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'assertNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'assertNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'flowNode') {\n          const fNode = new NestedFlowNode();\n          const result = fNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'flowNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'flowNode',\n            ...result.metadata,\n          };\n        }\n\n        if (node.type === 'setVarNode') {\n          const sNode = new SetVarNode();\n          const result = sNode.deserialize(node);\n          textData.graph.data.nodes[result.id] = {\n            type: 'setVarNode',\n            ...result.data,\n          };\n\n          textData.graph.metadata.nodes[result.id] = {\n            type: 'setVarNode',\n            ...result.metadata,\n          };\n        }\n      });\n    }\n\n    if (flowData.edges) {\n      const edges = flowDataCopy.edges;\n      textData.graph.data.edges = [];\n      textData.graph.metadata.edges = {};\n\n      edges.forEach((edge) => {\n        textData.graph.data.edges.push(`${edge.source} -> ${edge.target}`);\n\n        const { ['id']: _, ..._edge } = edge;\n        textData.graph.metadata.edges[edge.id] = _edge;\n      });\n    }\n\n    if (flowData.viewport) {\n      textData.graph.metadata.viewport = flowDataCopy.viewport;\n    }\n  }\n\n  return textData;\n};\n\nconst serialize = (textData) => {\n  const flowData = {};\n  flowData.nodes = [];\n  flowData.edges = [];\n  flowData.viewport = { x: 0, y: 0, zoom: 1 };\n\n  // we don't want to modify original object\n  const textDataCopy = cloneDeep(textData);\n  const version = textDataCopy.version;\n  if (version === 1) {\n    if (textDataCopy.graph.data) {\n      Object.entries(textDataCopy.graph.data.nodes).map(([key, value], index) => {\n        const id = key;\n\n        if (value.type === 'startNode') {\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const sNode = new StartNode();\n          const result = sNode.serialize(id, undefined, metadata);\n\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'authNode') {\n          const data = value.auth;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const aNode = new AuthNode();\n          const result = aNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'requestNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const rNode = new RequestNode();\n          const result = rNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'outputNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const oNode = new OutputNode();\n          const result = oNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'delayNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const dNode = new DelayNode();\n          const result = dNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'assertNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const dNode = new AssertNode();\n          const result = dNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'flowNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const fNode = new NestedFlowNode();\n          const result = fNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n\n        if (value.type === 'setVarNode') {\n          const data = value;\n          const metadata = textDataCopy.graph.metadata.nodes[id];\n          const cNode = new SetVarNode();\n          const result = cNode.serialize(id, data, metadata);\n          flowData.nodes.push(result);\n        }\n      });\n\n      Object.entries(textDataCopy.graph.metadata.edges).map(([key, value], index) => {\n        flowData.edges.push({\n          id: key,\n          ...value,\n        });\n      });\n\n      if (textDataCopy.graph.metadata.viewport) {\n        flowData.viewport = textDataCopy.graph.metadata.viewport;\n      }\n    }\n  } else {\n    throw new Error('Version not recognized');\n  }\n\n  return flowData;\n};\n\nmodule.exports = {\n  deserialize,\n  serialize,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/generate-request-body.js",
    "content": "const generateRequestBodyExample = (schema, level = 0, context = { processedSchemas: new Set() }) => {\n  if (!schema) return {};\n\n  if (schema.example !== undefined) {\n    return schema.example;\n  }\n\n  if (schema.enum) {\n    return schema.example || schema.enum[0];\n  }\n\n  if (schema.oneOf) {\n    return generateRequestBodyExample(schema.oneOf[0], level + 1, context);\n  }\n\n  if (schema.anyOf) {\n    return generateRequestBodyExample(schema.anyOf[0], level + 1, context);\n  }\n\n  if (schema.allOf) {\n    return generateAllOfExample(schema.allOf, level + 1, context);\n  }\n\n  switch (schema.type) {\n    case 'object':\n      return generateObjectExample(schema, level + 1, context);\n    case 'array':\n      return generateArrayExample(schema, level + 1, context);\n    case 'string':\n      return generateStringExample(schema);\n    case 'integer':\n      return generateIntegerExample(schema);\n    case 'number':\n      return generateNumberExample(schema);\n    case 'boolean':\n      return schema.example || true;\n    default:\n      return schema.example || null;\n  }\n};\n\nconst generateAllOfExample = (schemas, level, context) => {\n  const example = {};\n  schemas.forEach((subSchema) => {\n    const subExample = generateRequestBodyExample(subSchema, level, context);\n    Object.assign(example, subExample);\n  });\n  return example;\n};\n\nconst generateObjectExample = (schema, level, context) => {\n  if (schema.example !== undefined) {\n    return schema.example;\n  }\n\n  if (context.processedSchemas.has(schema) && level > 1) {\n    return {};\n  }\n  context.processedSchemas.add(schema);\n\n  const example = {};\n  const properties = schema.properties || {};\n\n  for (const [key, propertySchema] of Object.entries(properties)) {\n    example[key] = generateRequestBodyExample(propertySchema, level, context);\n  }\n\n  if (schema.additionalProperties) {\n    example.additionalProperty1 = generateRequestBodyExample(schema.additionalProperties, level, context);\n    example.additionalProperty2 = generateRequestBodyExample(schema.additionalProperties, level, context);\n  }\n\n  context.processedSchemas.delete(schema);\n  return example;\n};\n\nconst generateArrayExample = (schema, level, context) => {\n  if (schema.example !== undefined) {\n    return schema.example;\n  }\n  const itemsSchema = schema.items || {};\n  return [generateRequestBodyExample(itemsSchema, level, context)];\n};\n\nconst generateStringExample = (schema) => {\n  let example = String(schema.example || 'string');\n\n  if (schema.minLength || schema.maxLength) {\n    example = generateStringWithLengthConstraints(example, schema.minLength, schema.maxLength);\n  }\n\n  switch (schema.format) {\n    case 'date-time':\n      return schema.example || new Date().toISOString();\n    case 'date':\n      return schema.example || new Date().toISOString().split('T')[0];\n    case 'email':\n      return schema.example || 'example@example.com';\n    case 'uuid':\n      return schema.example || '123e4567-e89b-12d3-a456-426614174000';\n    case 'uri':\n      return schema.example || 'https://example.com';\n    case 'hostname':\n      return schema.example || 'example.com';\n    case 'ipv4':\n      return schema.example || '192.168.0.1';\n    case 'ipv6':\n      return schema.example || '2001:0db8:85a3:0000:0000:8a2e:0370:7334';\n    case 'byte':\n      return schema.example || btoa('example');\n    case 'binary':\n      return schema.example || 'binary data';\n    case 'password':\n      return schema.example || 'password';\n    default:\n      return example;\n  }\n};\n\nconst generateStringWithLengthConstraints = (str, minLength, maxLength) => {\n  if (minLength) {\n    while (str.length < minLength) {\n      str += 'a';\n    }\n  }\n  if (maxLength) {\n    str = str.substring(0, maxLength);\n  }\n  return str;\n};\n\nconst generateIntegerExample = (schema) => {\n  const min = schema.minimum || 0;\n  const max = schema.maximum || min + 100;\n  return schema.example || Math.floor(Math.random() * (max - min + 1)) + min;\n};\n\nconst generateNumberExample = (schema) => {\n  const min = schema.minimum || 0.0;\n  const max = schema.maximum || min + 100.0;\n  return schema.example || Math.random() * (max - min) + min;\n};\n\nmodule.exports = {\n  generateRequestBodyExample,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/src/utils/generate-request-parameters.js",
    "content": "function generateParameterExample(parameter) {\n  if (!parameter.schema) return {};\n\n  const schema = parameter.schema;\n\n  if (schema.enum) {\n    return schema.example || schema.enum[0];\n  }\n\n  switch (schema.type) {\n    case 'string':\n      return generateStringExample(schema);\n    case 'integer':\n      return generateIntegerExample(schema);\n    case 'number':\n      return generateNumberExample(schema);\n    case 'boolean':\n      return schema.example || true;\n    case 'array':\n      return generateArrayExample(schema);\n    default:\n      return schema.example || null;\n  }\n}\n\nfunction generateStringExample(schema) {\n  let example = schema.example || 'string';\n\n  if (schema.minLength || schema.maxLength) {\n    example = generateStringWithLengthConstraints(example, schema.minLength, schema.maxLength);\n  }\n\n  switch (schema.format) {\n    case 'date-time':\n      return schema.example || new Date().toISOString();\n    case 'date':\n      return schema.example || new Date().toISOString().split('T')[0];\n    case 'email':\n      return schema.example || 'example@example.com';\n    case 'uuid':\n      return schema.example || '123e4567-e89b-12d3-a456-426614174000';\n    case 'uri':\n      return schema.example || 'https://example.com';\n    case 'hostname':\n      return schema.example || 'example.com';\n    case 'ipv4':\n      return schema.example || '192.168.0.1';\n    case 'ipv6':\n      return schema.example || '2001:0db8:85a3:0000:0000:8a2e:0370:7334';\n    case 'byte':\n      return schema.example || btoa('example');\n    case 'binary':\n      return schema.example || 'binary data';\n    case 'password':\n      return schema.example || 'password';\n    default:\n      return example;\n  }\n}\n\nfunction generateStringWithLengthConstraints(str, minLength, maxLength) {\n  if (minLength) {\n    while (str.length < minLength) {\n      str += 'a';\n    }\n  }\n  if (maxLength) {\n    str = str.substring(0, maxLength);\n  }\n  return str;\n}\n\nfunction generateIntegerExample(schema) {\n  const min = schema.minimum || 0;\n  const max = schema.maximum || min + 100;\n  return schema.example || Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction generateNumberExample(schema) {\n  const min = schema.minimum || 0.0;\n  const max = schema.maximum || min + 100.0;\n  return schema.example || Math.random() * (max - min) + min;\n}\n\nfunction generateArrayExample(schema) {\n  const itemsSchema = schema.items || {};\n  return [generateParameterExample({ schema: itemsSchema })];\n}\n\nconst generatePathParamsExample = (parameters) => {\n  const examples = {};\n  parameters.forEach((param) => {\n    examples[param.name] = generateParameterExample(param);\n  });\n  return new URLSearchParams(examples);\n};\n\nconst generateQueryParamsExample = (parameters) => {\n  const examples = {};\n  parameters.forEach((param) => {\n    examples[param.name] = generateParameterExample(param);\n  });\n  return new URLSearchParams(examples);\n};\n\nmodule.exports = {\n  generateParameterExample,\n  generatePathParamsExample,\n  generateQueryParamsExample,\n};\n"
  },
  {
    "path": "packages/flowtest-electron/tests/store/collection-store.test.js",
    "content": "const path = require('path');\n\nconst Collections = require('../../src/store/collection');\nconst createDirectory = require('../../src/utils/filemanager/createdirectory');\nconst deleteDirectory = require('../../src/utils/filemanager/deletedirectory');\n\ndescribe('collection-store', () => {\n  it('should create and remove collection', async () => {\n    const store = new Collections();\n    const newCollection = {\n      id: '1234',\n      name: 'test',\n      pathname: `${__dirname}/test`,\n      openapi_spec: '',\n      nodes: '{}',\n    };\n\n    const newestCollection = {\n      id: '12345',\n      name: 'test1',\n      pathname: `${__dirname}/test1`,\n      openapi_spec: '',\n      nodes: '{}',\n    };\n\n    store.removeAll();\n    expect(store.getAll()).toEqual([]);\n\n    // adding a collection whose directory doesn't exist\n    store.add(newCollection);\n    expect(store.getAll()).toEqual([]);\n\n    createDirectory('test', __dirname);\n    store.add(newCollection);\n    expect(store.getAll()).toEqual([newCollection]);\n\n    createDirectory('test1', __dirname);\n    store.add(newestCollection);\n    expect(store.getAll()).toEqual([newCollection, newestCollection]);\n\n    store.remove(newCollection);\n    expect(store.getAll()).toEqual([newestCollection]);\n\n    store.remove(newestCollection);\n    expect(store.getAll()).toEqual([]);\n\n    deleteDirectory(`${__dirname}/test`);\n    deleteDirectory(`${__dirname}/test1`);\n  });\n\n  it('collection set should be unique by pathname', async () => {\n    const store = new Collections();\n    const newCollection = {\n      id: '1234',\n      name: 'test',\n      pathname: `${__dirname}/test`,\n      openapi_spec: '',\n      nodes: '{}',\n    };\n\n    const newestCollection = {\n      id: '12345',\n      name: 'test',\n      pathname: `${__dirname}/test`,\n      openapi_spec: '',\n      nodes: '{}',\n    };\n\n    store.removeAll();\n    expect(store.getAll()).toEqual([]);\n\n    createDirectory('test', __dirname);\n    store.add(newCollection);\n    expect(store.getAll()).toEqual([newCollection]);\n\n    // collection in the store should be unique by path\n    store.add(newestCollection);\n    expect(store.getAll()).toEqual([newCollection]);\n\n    store.remove(newCollection);\n    expect(store.getAll()).toEqual([]);\n\n    deleteDirectory(`${__dirname}/test`);\n  });\n});\n"
  },
  {
    "path": "packages/flowtest-electron/tests/store/settings-store.test.js",
    "content": "const Settings = require('../../src/store/settings');\n\ndescribe('settings-store', () => {\n  it('should create and get settings', async () => {\n    const store = new Settings();\n    store.clearAll();\n\n    let settings = store.getAll();\n    expect(settings.logSyncConfig).toEqual({});\n    expect(settings.genAIUsageDisclaimer).toEqual(false);\n\n    // adding a collection whose directory doesn't exist\n    store.addLogSyncConfig(true, 'http://localhost:3000', 'access_id', 'access_key');\n    store.addGenAIUsageDisclaimer(true);\n\n    settings = store.getAll();\n    const config = settings.logSyncConfig;\n    expect(config.enabled).toEqual(true);\n    expect(config.hostUrl).toEqual('http://localhost:3000');\n    expect(config.accessId).toEqual('access_id');\n    expect(config.accessKey).toEqual('access_key');\n\n    expect(settings.genAIUsageDisclaimer).toEqual(true);\n  });\n});\n"
  },
  {
    "path": "packages/flowtest-electron/tests/test.yaml",
    "content": "openapi: 3.0.3\ninfo:\n  title: Swagger Petstore - OpenAPI 3.0\n  description: |-\n    This is a sample Pet Store Server based on the OpenAPI 3.0 specification.  You can find out more about\n    Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\n    You can now help us improve the API whether it's by making changes to the definition itself or to the code.\n    That way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\n    _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_\n    \n    Some useful links:\n    - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n    - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)\n  termsOfService: http://swagger.io/terms/\n  contact:\n    email: apiteam@swagger.io\n  license:\n    name: Apache 2.0\n    url: http://www.apache.org/licenses/LICENSE-2.0.html\n  version: 1.0.11\nexternalDocs:\n  description: Find out more about Swagger\n  url: http://swagger.io\nservers:\n  - url: https://petstore3.swagger.io/api/v3\ntags:\n  - name: pet\n    description: Everything about your Pets\n    externalDocs:\n      description: Find out more\n      url: http://swagger.io\n  - name: store\n    description: Access to Petstore orders\n    externalDocs:\n      description: Find out more about our store\n      url: http://swagger.io\n  - name: user\n    description: Operations about user\npaths:\n  /pet:\n    put:\n      tags:\n        - pet\n      summary: Update an existing pet\n      description: Update an existing pet by Id\n      operationId: updatePet\n      requestBody:\n        description: Update an existent pet in the store\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/Pet'\n          application/xml:\n            schema:\n              $ref: '#/components/schemas/Pet'\n          application/x-www-form-urlencoded:\n            schema:\n              $ref: '#/components/schemas/Pet'\n        required: true\n      responses:\n        '200':\n          description: Successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Pet'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/Pet'\n        '400':\n          description: Invalid ID supplied\n        '404':\n          description: Pet not found\n        '405':\n          description: Validation exception\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n    post:\n      tags:\n        - pet\n      summary: Add a new pet to the store\n      description: Add a new pet to the store\n      operationId: addPet\n      requestBody:\n        description: Create a new pet in the store\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/Pet'\n          application/xml:\n            schema:\n              $ref: '#/components/schemas/Pet'\n          application/x-www-form-urlencoded:\n            schema:\n              $ref: '#/components/schemas/Pet'\n        required: true\n      responses:\n        '200':\n          description: Successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Pet'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/Pet'\n        '405':\n          description: Invalid input\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n  /pet/findByStatus:\n    get:\n      tags:\n        - pet\n      summary: Finds Pets by status\n      description: Multiple status values can be provided with comma separated strings\n      operationId: findPetsByStatus\n      parameters:\n        - name: status\n          in: query\n          description: Status values that need to be considered for filter\n          required: false\n          explode: true\n          schema:\n            type: string\n            default: available\n            enum:\n              - available\n              - pending\n              - sold\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Pet'          \n            application/xml:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Pet'\n        '400':\n          description: Invalid status value\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n  /pet/findByTags:\n    get:\n      tags:\n        - pet\n      summary: Finds Pets by tags\n      description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.\n      operationId: findPetsByTags\n      parameters:\n        - name: tags\n          in: query\n          description: Tags to filter by\n          required: false\n          explode: true\n          schema:\n            type: array\n            items:\n              type: string\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Pet'          \n            application/xml:\n              schema:\n                type: array\n                items:\n                  $ref: '#/components/schemas/Pet'\n        '400':\n          description: Invalid tag value\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n  /pet/{petId}:\n    get:\n      tags:\n        - pet\n      summary: Find pet by ID\n      description: Returns a single pet\n      operationId: getPetById\n      parameters:\n        - name: petId\n          in: path\n          description: ID of pet to return\n          required: true\n          schema:\n            type: integer\n            format: int64\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Pet'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/Pet'\n        '400':\n          description: Invalid ID supplied\n        '404':\n          description: Pet not found\n      security:\n        - api_key: []\n        - petstore_auth:\n            - write:pets\n            - read:pets\n    post:\n      tags:\n        - pet\n      summary: Updates a pet in the store with form data\n      description: ''\n      operationId: updatePetWithForm\n      parameters:\n        - name: petId\n          in: path\n          description: ID of pet that needs to be updated\n          required: true\n          schema:\n            type: integer\n            format: int64\n        - name: name\n          in: query\n          description: Name of pet that needs to be updated\n          schema:\n            type: string\n        - name: status\n          in: query\n          description: Status of pet that needs to be updated\n          schema:\n            type: string\n      responses:\n        '405':\n          description: Invalid input\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n    delete:\n      tags:\n        - pet\n      summary: Deletes a pet\n      description: delete a pet\n      operationId: deletePet\n      parameters:\n        - name: api_key\n          in: header\n          description: ''\n          required: false\n          schema:\n            type: string\n        - name: petId\n          in: path\n          description: Pet id to delete\n          required: true\n          schema:\n            type: integer\n            format: int64\n      responses:\n        '400':\n          description: Invalid pet value\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n  /pet/{petId}/uploadImage:\n    post:\n      tags:\n        - pet\n      summary: uploads an image\n      description: ''\n      operationId: uploadFile\n      parameters:\n        - name: petId\n          in: path\n          description: ID of pet to update\n          required: true\n          schema:\n            type: integer\n            format: int64\n        - name: additionalMetadata\n          in: query\n          description: Additional Metadata\n          required: false\n          schema:\n            type: string\n      requestBody:\n        content:\n          application/octet-stream:\n            schema:\n              type: string\n              format: binary\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/ApiResponse'\n      security:\n        - petstore_auth:\n            - write:pets\n            - read:pets\n  /store/inventory:\n    get:\n      tags:\n        - store\n      summary: Returns pet inventories by status\n      description: Returns a map of status codes to quantities\n      operationId: getInventory\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                type: object\n                additionalProperties:\n                  type: integer\n                  format: int32\n      security:\n        - api_key: []\n  /store/order:\n    post:\n      tags:\n        - store\n      summary: Place an order for a pet\n      description: Place a new order in the store\n      operationId: placeOrder\n      requestBody:\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/Order'\n          application/xml:\n            schema:\n              $ref: '#/components/schemas/Order'\n          application/x-www-form-urlencoded:\n            schema:\n              $ref: '#/components/schemas/Order'\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Order'\n        '405':\n          description: Invalid input\n  /store/order/{orderId}:\n    get:\n      tags:\n        - store\n      summary: Find purchase order by ID\n      description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.\n      operationId: getOrderById\n      parameters:\n        - name: orderId\n          in: path\n          description: ID of order that needs to be fetched\n          required: true\n          schema:\n            type: integer\n            format: int64\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Order'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/Order'\n        '400':\n          description: Invalid ID supplied\n        '404':\n          description: Order not found\n    delete:\n      tags:\n        - store\n      summary: Delete purchase order by ID\n      description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors\n      operationId: deleteOrder\n      parameters:\n        - name: orderId\n          in: path\n          description: ID of the order that needs to be deleted\n          required: true\n          schema:\n            type: integer\n            format: int64\n      responses:\n        '400':\n          description: Invalid ID supplied\n        '404':\n          description: Order not found\n  /user:\n    post:\n      tags:\n        - user\n      summary: Create user\n      description: This can only be done by the logged in user.\n      operationId: createUser\n      requestBody:\n        description: Created user object\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/User'\n          application/xml:\n            schema:\n              $ref: '#/components/schemas/User'\n          application/x-www-form-urlencoded:\n            schema:\n              $ref: '#/components/schemas/User'\n      responses:\n        default:\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/User'\n            application/xml:\n              schema:\n                $ref: '#/components/schemas/User'\n  /user/createWithList:\n    post:\n      tags:\n        - user\n      summary: Creates list of users with given input array\n      description: Creates list of users with given input array\n      operationId: createUsersWithListInput\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                $ref: '#/components/schemas/User'\n      responses:\n        '200':\n          description: Successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/User'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/User'\n        default:\n          description: successful operation\n  /user/login:\n    get:\n      tags:\n        - user\n      summary: Logs user into the system\n      description: ''\n      operationId: loginUser\n      parameters:\n        - name: username\n          in: query\n          description: The user name for login\n          required: false\n          schema:\n            type: string\n        - name: password\n          in: query\n          description: The password for login in clear text\n          required: false\n          schema:\n            type: string\n      responses:\n        '200':\n          description: successful operation\n          headers:\n            X-Rate-Limit:\n              description: calls per hour allowed by the user\n              schema:\n                type: integer\n                format: int32\n            X-Expires-After:\n              description: date in UTC when token expires\n              schema:\n                type: string\n                format: date-time\n          content:\n            application/xml:\n              schema:\n                type: string\n            application/json:\n              schema:\n                type: string\n        '400':\n          description: Invalid username/password supplied\n  /user/logout:\n    get:\n      tags:\n        - user\n      summary: Logs out current logged in user session\n      description: ''\n      operationId: logoutUser\n      parameters: []\n      responses:\n        default:\n          description: successful operation\n  /user/{username}:\n    get:\n      tags:\n        - user\n      summary: Get user by user name\n      description: ''\n      operationId: getUserByName\n      parameters:\n        - name: username\n          in: path\n          description: 'The name that needs to be fetched. Use user1 for testing. '\n          required: true\n          schema:\n            type: string\n      responses:\n        '200':\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/User'          \n            application/xml:\n              schema:\n                $ref: '#/components/schemas/User'\n        '400':\n          description: Invalid username supplied\n        '404':\n          description: User not found\n    put:\n      tags:\n        - user\n      summary: Update user\n      description: This can only be done by the logged in user.\n      operationId: updateUser\n      parameters:\n        - name: username\n          in: path\n          description: name that need to be deleted\n          required: true\n          schema:\n            type: string\n      requestBody:\n        description: Update an existent user in the store\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/User'\n          application/xml:\n            schema:\n              $ref: '#/components/schemas/User'\n          application/x-www-form-urlencoded:\n            schema:\n              $ref: '#/components/schemas/User'\n      responses:\n        default:\n          description: successful operation\n    delete:\n      tags:\n        - user\n      summary: Delete user\n      description: This can only be done by the logged in user.\n      operationId: deleteUser\n      parameters:\n        - name: username\n          in: path\n          description: The name that needs to be deleted\n          required: true\n          schema:\n            type: string\n      responses:\n        '400':\n          description: Invalid username supplied\n        '404':\n          description: User not found\ncomponents:\n  schemas:\n    Order:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n          example: 10\n        petId:\n          type: integer\n          format: int64\n          example: 198772\n        quantity:\n          type: integer\n          format: int32\n          example: 7\n        shipDate:\n          type: string\n          format: date-time\n        status:\n          type: string\n          description: Order Status\n          example: approved\n          enum:\n            - placed\n            - approved\n            - delivered\n        complete:\n          type: boolean\n      xml:\n        name: order\n    Customer:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n          example: 100000\n        username:\n          type: string\n          example: fehguy\n        address:\n          type: array\n          xml:\n            name: addresses\n            wrapped: true\n          items:\n            $ref: '#/components/schemas/Address'\n      xml:\n        name: customer\n    Address:\n      type: object\n      properties:\n        street:\n          type: string\n          example: 437 Lytton\n        city:\n          type: string\n          example: Palo Alto\n        state:\n          type: string\n          example: CA\n        zip:\n          type: string\n          example: '94301'\n      xml:\n        name: address\n    Category:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n          example: 1\n        name:\n          type: string\n          example: Dogs\n      xml:\n        name: category\n    User:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n          example: 10\n        username:\n          type: string\n          example: theUser\n        firstName:\n          type: string\n          example: John\n        lastName:\n          type: string\n          example: James\n        email:\n          type: string\n          example: john@email.com\n        password:\n          type: string\n          example: '12345'\n        phone:\n          type: string\n          example: '12345'\n        userStatus:\n          type: integer\n          description: User Status\n          format: int32\n          example: 1\n      xml:\n        name: user\n    Tag:\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n        name:\n          type: string\n      xml:\n        name: tag\n    Pet:\n      required:\n        - name\n        - photoUrls\n      type: object\n      properties:\n        id:\n          type: integer\n          format: int64\n          example: 10\n        name:\n          type: string\n          example: doggie\n        category:\n          $ref: '#/components/schemas/Category'\n        photoUrls:\n          type: array\n          xml:\n            wrapped: true\n          items:\n            type: string\n            xml:\n              name: photoUrl\n        tags:\n          type: array\n          xml:\n            wrapped: true\n          items:\n            $ref: '#/components/schemas/Tag'\n        status:\n          type: string\n          description: pet status in the store\n          enum:\n            - available\n            - pending\n            - sold\n      xml:\n        name: pet\n    ApiResponse:\n      type: object\n      properties:\n        code:\n          type: integer\n          format: int32\n        type:\n          type: string\n        message:\n          type: string\n      xml:\n        name: '##default'\n  requestBodies:\n    Pet:\n      description: Pet object that needs to be added to the store\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/Pet'\n        application/xml:\n          schema:\n            $ref: '#/components/schemas/Pet'\n    UserArray:\n      description: List of user object\n      content:\n        application/json:\n          schema:\n            type: array\n            items:\n              $ref: '#/components/schemas/User'\n  securitySchemes:\n    petstore_auth:\n      type: oauth2\n      flows:\n        implicit:\n          authorizationUrl: https://petstore3.swagger.io/oauth/authorize\n          scopes:\n            write:pets: modify pets in your account\n            read:pets: read your pets\n    api_key:\n      type: apiKey\n      name: api_key\n      in: header"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/collection-parser.test.js",
    "content": "const SwaggerParser = require('@apidevtools/swagger-parser');\nconst JsonRefs = require('json-refs');\nconst parseOpenAPISpec = require('../../src/utils/collection');\n\ndescribe('parse', () => {\n  it('should add do basic parsing', async () => {\n    let api = await SwaggerParser.validate('tests/test.yaml');\n    console.log('API name: %s, Version: %s', api.info.title, api.info.version);\n    const resolvedSpec = await JsonRefs.resolveRefs(api);\n    const nodes = parseOpenAPISpec(resolvedSpec.resolved);\n    expect(nodes).toEqual(expected);\n  });\n});\n\nconst expected = [\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet',\n    description: 'Update an existing pet',\n    operationId: 'updatePet',\n    requestType: 'PUT',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet',\n    description: 'Add a new pet to the store',\n    operationId: 'addPet',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/findByStatus?status={{status}}',\n    description: 'Finds Pets by status',\n    operationId: 'findPetsByStatus',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/findByTags?tags={{tags}}',\n    description: 'Finds Pets by tags',\n    operationId: 'findPetsByTags',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',\n    description: 'Find pet by ID',\n    operationId: 'getPetById',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',\n    description: 'Updates a pet in the store with form data',\n    operationId: 'updatePetWithForm',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',\n    description: 'Deletes a pet',\n    operationId: 'deletePet',\n    requestType: 'DELETE',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}/uploadImage',\n    description: 'uploads an image',\n    operationId: 'uploadFile',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/store/inventory',\n    description: 'Returns pet inventories by status',\n    operationId: 'getInventory',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/store/order',\n    description: 'Place an order for a pet',\n    operationId: 'placeOrder',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',\n    description: 'Find purchase order by ID',\n    operationId: 'getOrderById',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',\n    description: 'Delete purchase order by ID',\n    operationId: 'deleteOrder',\n    requestType: 'DELETE',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user',\n    description: 'Create user',\n    operationId: 'createUser',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/createWithList',\n    description: 'Creates list of users with given input array',\n    operationId: 'createUsersWithListInput',\n    requestType: 'POST',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/login?username={{username}}&password={{password}}',\n    description: 'Logs user into the system',\n    operationId: 'loginUser',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/logout',\n    description: 'Logs out current logged in user session',\n    operationId: 'logoutUser',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',\n    description: 'Get user by user name',\n    operationId: 'getUserByName',\n    requestType: 'GET',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',\n    description: 'Update user',\n    operationId: 'updateUser',\n    requestType: 'PUT',\n  },\n  {\n    url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',\n    description: 'Delete user',\n    operationId: 'deleteUser',\n    requestType: 'DELETE',\n  },\n];\n"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/filemanager.test.js",
    "content": "const createDirectory = require('../../src/utils/filemanager/createdirectory');\nconst deleteDirectory = require('../../src/utils/filemanager/deletedirectory');\nconst path = require('path');\nconst createFile = require('../../src/utils/filemanager/createfile');\nconst readFile = require('../../src/utils/filemanager/readfile');\nconst deleteFile = require('../../src/utils/filemanager/deletefile');\nconst { pathExists } = require('../../src/utils/filemanager/filesystem');\n\nconst DIRECTORY_NAME = 'testDir';\n\ndescribe('file-manager', () => {\n  it('should create and delete directory', async () => {\n    let result = createDirectory(DIRECTORY_NAME, __dirname);\n    expect(result).toEqual(path.join(__dirname, DIRECTORY_NAME));\n\n    // directory already exists\n    expect(() => {\n      createDirectory(DIRECTORY_NAME, __dirname);\n    }).toThrow(Error);\n\n    deleteDirectory(path.join(__dirname, DIRECTORY_NAME));\n    expect(pathExists(path.join(__dirname, DIRECTORY_NAME))).toEqual(false);\n\n    // directory doesn't exist\n    expect(() => {\n      deleteDirectory(path.join(__dirname, DIRECTORY_NAME));\n    }).toThrow(Error);\n  });\n\n  it('should create and delete files', async () => {\n    let result = createDirectory(DIRECTORY_NAME, __dirname);\n    expect(result).toEqual(path.join(__dirname, DIRECTORY_NAME));\n\n    createFile('test.flow', path.join(__dirname, DIRECTORY_NAME), '{\"k1\":\"v1\"}');\n    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test.flow'))).toEqual(true);\n\n    // read file\n    const rContent = readFile(path.join(__dirname, DIRECTORY_NAME, 'test.flow'));\n    expect(rContent).toEqual('{\"k1\":\"v1\"}');\n\n    // file already exists\n    expect(() => {\n      createFile('test.flow', path.join(__dirname, DIRECTORY_NAME), '{\"k1\":\"v1\"}');\n    }).toThrow(Error);\n\n    createFile('test1.flow', path.join(__dirname, DIRECTORY_NAME), '{\"k1\":\"v1\"}');\n    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'))).toEqual(true);\n\n    // delete file\n    deleteFile(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'));\n    expect(pathExists(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'))).toEqual(false);\n\n    // delete file: file no longer exists\n    expect(() => {\n      deleteFile(path.join(__dirname, DIRECTORY_NAME, 'test1.flow'));\n    }).toThrow(Error);\n\n    // delete directory\n    deleteDirectory(path.join(__dirname, DIRECTORY_NAME));\n    expect(pathExists(path.join(__dirname, DIRECTORY_NAME))).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/flowtest-ai.test.js",
    "content": "const fs = require('fs');\nconst SwaggerParser = require('@apidevtools/swagger-parser');\nconst JsonRefs = require('json-refs');\nconst FlowtestAI = require('../../src/ai/flowtestai');\n\ndescribe('generate', () => {\n  it('should generate functions using openai', async () => {\n    const f = new FlowtestAI();\n    const USER_INSTRUCTION =\n      'Add a new pet to the store. \\\n            Then get the created pet. \\\n            Then get pet with status as available.';\n    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });\n    let api = await SwaggerParser.validate('tests/test.yaml');\n    console.log('API name: %s, Version: %s', api.info.title, api.info.version);\n    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;\n\n    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {\n      name: 'OPENAI',\n      apiKey: '',\n    });\n    const nodeNames = result.map((node) => node.name);\n    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);\n  }, 60000);\n\n  it('should generate functions using bedrock', async () => {\n    const f = new FlowtestAI();\n    const USER_INSTRUCTION =\n      'Add a new pet to the store. \\\n            Then get the created pet. \\\n            Then get pet with status as available.';\n    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });\n    let api = await SwaggerParser.validate('tests/test.yaml');\n    console.log('API name: %s, Version: %s', api.info.title, api.info.version);\n    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;\n\n    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {\n      name: 'BEDROCK_CLAUDE',\n      apiKey: {\n        accessKeyId: '',\n        secretAccessKey: '',\n      },\n    });\n    const nodeNames = result.map((node) => node.name);\n    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);\n  }, 60000);\n\n  it('should generate functions using gemini', async () => {\n    const f = new FlowtestAI();\n    const USER_INSTRUCTION =\n      'Add a new pet to the store. \\\n            Then get the created pet. \\\n            Then get pet with status as available.';\n    //const testYaml = fs.readFileSync('tests/test.yaml', { encoding: 'utf8', flag: 'r' });\n    let api = await SwaggerParser.validate('tests/test.yaml');\n    console.log('API name: %s, Version: %s', api.info.title, api.info.version);\n    const resolvedSpec = (await JsonRefs.resolveRefs(api)).resolved;\n\n    let result = await f.generate(resolvedSpec, USER_INSTRUCTION, {\n      name: 'GEMINI',\n      apiKey: '',\n    });\n    const nodeNames = result.map((node) => node.name);\n    expect(nodeNames).toEqual(['addPet', 'getPetById', 'findPetsByStatus']);\n  }, 60000);\n});\n"
  },
  {
    "path": "packages/flowtest-electron/tests/utils/flowtest-parser.test.js",
    "content": "const { deserialize, serialize } = require('../../src/utils/flowparser/parser');\n\ndescribe('FlowTest parser', () => {\n  it('should parse correctly', async () => {\n    const flowData = {\n      nodes: [\n        {\n          id: '0',\n          type: 'startNode',\n          position: {\n            x: 150,\n            y: 150,\n          },\n          deletable: false,\n          width: 90,\n          height: 60,\n        },\n        {\n          id: '1',\n          data: {\n            type: 'basic-auth',\n            username: '{{accessId}}',\n            password: '{{accessKey}}',\n          },\n          type: 'authNode',\n          position: {\n            x: 402,\n            y: 138,\n          },\n          deletable: false,\n          width: 214,\n          height: 199,\n          selected: false,\n          dragging: false,\n          positionAbsolute: {\n            x: 402,\n            y: 138,\n          },\n        },\n        {\n          id: '2',\n          data: {\n            url: 'https://petstore3.swagger.io/api/v3/pet',\n            description: 'Add a new pet to the store',\n            operationId: 'addPet',\n            requestType: 'POST',\n            tags: ['pet'],\n            type: 'requestNode',\n            requestBody: {\n              type: 'raw-json',\n              body: '{\"id\":1,\"name\":\"Max\",\"category\":{\"id\":1,\"name\":\"Dog\"},\"photoUrls\":[\"https://example.com/max.jpg\"],\"tags\":[{\"id\":1,\"name\":\"friendly\"}],\"status\":\"available\"}',\n            },\n            postRespVars: {\n              petId: {\n                type: 'Select',\n                value: 'id',\n              },\n            },\n          },\n          type: 'requestNode',\n          position: {\n            x: 747.1300785316197,\n            y: 71.34121814738344,\n          },\n          width: 402,\n          height: 528,\n          selected: false,\n          positionAbsolute: {\n            x: 747.1300785316197,\n            y: 71.34121814738344,\n          },\n          dragging: false,\n        },\n        {\n          id: '3',\n          data: {\n            url: 'https://petstore3.swagger.io/api/v3/pet/findByStatus?status={{status}}',\n            description: 'Finds Pets by status',\n            operationId: 'findPetsByStatus',\n            requestType: 'GET',\n            tags: ['pet'],\n            type: 'requestNode',\n            preReqVars: {\n              status: {\n                type: 'String',\n                value: 'available',\n              },\n            },\n          },\n          type: 'requestNode',\n          position: {\n            x: 1217.0371813974814,\n            y: 75.76040720254832,\n          },\n          width: 406,\n          height: 392,\n          selected: false,\n          positionAbsolute: {\n            x: 1217.0371813974814,\n            y: 75.76040720254832,\n          },\n          dragging: false,\n        },\n        {\n          id: '8',\n          type: 'delayNode',\n          position: {\n            x: 1671.5283794101285,\n            y: 181.3397155683441,\n          },\n          data: {\n            description: 'Add a certain delay before next computation.',\n            type: 'delayNode',\n            delay: '6',\n          },\n          width: 214,\n          height: 110,\n          selected: false,\n          positionAbsolute: {\n            x: 1671.5283794101285,\n            y: 181.3397155683441,\n          },\n          dragging: false,\n        },\n        {\n          id: '9',\n          type: 'assertNode',\n          position: {\n            x: 1958.6425053074213,\n            y: 59.84168648963893,\n          },\n          data: {\n            description: 'Assert on conditional expressions.',\n            type: 'assertNode',\n            variables: {\n              var1: {\n                type: 'String',\n                value: '1',\n              },\n              var2: {\n                type: 'String',\n                value: '1',\n              },\n            },\n            operator: 'isEqualTo',\n          },\n          width: 296,\n          height: 328,\n          selected: false,\n          positionAbsolute: {\n            x: 1958.6425053074213,\n            y: 59.84168648963893,\n          },\n          dragging: false,\n        },\n        {\n          id: '10',\n          type: 'flowNode',\n          position: {\n            x: 2427.6600136878287,\n            y: 94.18397266812065,\n          },\n          data: {\n            description: 'Helps to create nested flows',\n            type: 'flowNode',\n            relativePath: 'sample.flow',\n          },\n          width: 164,\n          height: 108,\n          selected: true,\n          positionAbsolute: {\n            x: 2427.6600136878287,\n            y: 94.18397266812065,\n          },\n          dragging: false,\n        },\n        {\n          id: '11',\n          type: 'outputNode',\n          position: {\n            x: 2366.8251067430897,\n            y: 268.8390280901134,\n          },\n          data: {\n            description: 'Displays any data received.',\n            type: 'outputNode',\n          },\n          width: 276,\n          height: 386,\n          selected: false,\n          positionAbsolute: {\n            x: 2366.8251067430897,\n            y: 268.8390280901134,\n          },\n          dragging: false,\n        },\n      ],\n      edges: [\n        {\n          id: 'reactflow__edge-0-1',\n          source: '0',\n          sourceHandle: null,\n          target: '1',\n          targetHandle: null,\n          type: 'buttonedge',\n        },\n        {\n          source: '1',\n          sourceHandle: null,\n          target: '2',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-1-2',\n        },\n        {\n          source: '2',\n          sourceHandle: null,\n          target: '3',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-2-3',\n        },\n        {\n          source: '3',\n          sourceHandle: null,\n          target: '8',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-3-8',\n        },\n        {\n          source: '8',\n          sourceHandle: null,\n          target: '9',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-8-9',\n        },\n        {\n          source: '9',\n          sourceHandle: 'false',\n          target: '11',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-9false-11',\n        },\n        {\n          source: '9',\n          sourceHandle: 'true',\n          target: '10',\n          targetHandle: null,\n          type: 'buttonedge',\n          id: 'reactflow__edge-9true-10',\n        },\n      ],\n      viewport: { x: 0.1, y: 0.2, zoom: 1.9876 },\n    };\n\n    const textData = deserialize(flowData);\n    const _flowData = serialize(textData);\n    //console.log(JSON.stringify(textData));\n    //console.log(JSON.stringify(_flowData));\n\n    expect(_flowData.nodes).toEqual(flowData.nodes);\n    expect(_flowData.edges).toEqual(flowData.edges);\n    expect(_flowData.viewport).toEqual(flowData.viewport);\n  });\n});\n"
  },
  {
    "path": "packages/flowtest-electron/tests/watcher.test.js",
    "content": "const Watcher = require('../src/app/watcher');\nconst path = require('path');\nconst createDirectory = require('../src/utils/filemanager/createdirectory');\nconst deleteDirectory = require('../src/utils/filemanager/deletedirectory');\nconst createFile = require('../src/utils/filemanager/createfile');\nconst updateFile = require('../src/utils/filemanager/updatefile');\n\ndescribe('watcher', () => {\n  const watcher = new Watcher();\n  const DIRECTORY_NAME = 'collection';\n  const collectionPath = path.join(__dirname, DIRECTORY_NAME);\n  const collectionId = '1234';\n  const mainWindow = {\n    webContents: {\n      send: jest.fn((channel, ...args) => {}),\n    },\n  };\n\n  beforeEach(() => {\n    mainWindow.webContents.send.mockClear();\n  });\n\n  it('should watch add and delete directory', async () => {\n    watcher.addDirectory(mainWindow, path.join(collectionPath, 'folder1'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:add-directory',\n      {\n        name: 'folder1',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1',\n      },\n      '1234',\n      ['folder1'],\n      '/',\n    );\n\n    watcher.addDirectory(mainWindow, path.join(collectionPath, 'environments'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n\n    watcher.addDirectory(\n      mainWindow,\n      path.join(collectionPath, 'folder1', 'folder2', 'folder3'),\n      collectionId,\n      collectionPath,\n    );\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:add-directory',\n      {\n        name: 'folder3',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1/folder2/folder3',\n      },\n      '1234',\n      ['folder1', 'folder2', 'folder3'],\n      '/',\n    );\n\n    watcher.unlinkDir(mainWindow, path.join(collectionPath, 'folder1'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:delete-directory',\n      {\n        name: 'folder1',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1',\n      },\n      '1234',\n    );\n  });\n\n  it('should not do anything for environments folder', async () => {\n    watcher.addDirectory(mainWindow, path.join(collectionPath, 'environments'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(0);\n\n    watcher.unlinkDir(mainWindow, path.join(collectionPath, 'environments'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(0);\n  });\n\n  it('should watch add, change and delete flowtest files', async () => {\n    watcher.add(mainWindow, path.join(collectionPath, 'test.flow'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:create-flowtest',\n      {\n        name: 'test.flow',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/test.flow',\n        sep: '/',\n        subDirectories: [],\n      },\n      '1234',\n    );\n\n    watcher.add(mainWindow, path.join(collectionPath, 'folder1', 'folder2', 'test.flow'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:create-flowtest',\n      {\n        name: 'test.flow',\n        pathname:\n          '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1/folder2/test.flow',\n        sep: '/',\n        subDirectories: ['folder1', 'folder2'],\n      },\n      '1234',\n    );\n\n    watcher.change(\n      mainWindow,\n      path.join(collectionPath, 'folder1', 'folder2', 'test.flow'),\n      collectionId,\n      collectionPath,\n    );\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:update-flowtest',\n      {\n        name: 'test.flow',\n        pathname:\n          '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1/folder2/test.flow',\n      },\n      '1234',\n    );\n\n    watcher.unlink(\n      mainWindow,\n      path.join(collectionPath, 'folder1', 'folder2', 'test.flow'),\n      collectionId,\n      collectionPath,\n    );\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:delete-flowtest',\n      {\n        name: 'test.flow',\n        pathname:\n          '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/folder1/folder2/test.flow',\n      },\n      '1234',\n    );\n  });\n\n  it('should watch add, change and delete env files', async () => {\n    createDirectory(DIRECTORY_NAME, __dirname);\n    createDirectory('environments', collectionPath);\n    createFile('test.env', path.join(collectionPath, 'environments'), 'k1=v1\\nk2=v2\\nk3=v3');\n    watcher.add(mainWindow, path.join(collectionPath, 'environments', 'test.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:addOrUpdate-environment',\n      {\n        name: 'test.env',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/environments/test.env',\n        variables: {\n          k1: 'v1',\n          k2: 'v2',\n          k3: 'v3',\n        },\n      },\n      '1234',\n    );\n\n    //environments folder is the source of truth for env files\n    mainWindow.webContents.send.mockClear();\n    watcher.add(mainWindow, path.join(collectionPath, 'test.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(0);\n\n    updateFile(path.join(collectionPath, 'environments', 'test.env'), 'k2=v2\\nk4=v4\\nk6=v6');\n    watcher.change(mainWindow, path.join(collectionPath, 'environments', 'test.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:addOrUpdate-environment',\n      {\n        name: 'test.env',\n        pathname: '/Users/sjain/projects/FlowTest/packages/flowtest-electron/tests/collection/environments/test.env',\n        variables: {\n          k2: 'v2',\n          k4: 'v4',\n          k6: 'v6',\n        },\n      },\n      '1234',\n    );\n\n    deleteDirectory(collectionPath);\n  });\n\n  it('should watch add and change dotenv file', async () => {\n    createDirectory(DIRECTORY_NAME, __dirname);\n    createFile('.env', collectionPath, 'k1=v1\\nk2=v2\\nk3=v3');\n    watcher.add(mainWindow, path.join(collectionPath, '.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:addOrUpdate-dotEnvironment',\n      {\n        k1: 'v1',\n        k2: 'v2',\n        k3: 'v3',\n      },\n      '1234',\n    );\n\n    //.env is only allowed in collection root path\n    mainWindow.webContents.send.mockClear();\n    watcher.add(mainWindow, path.join(collectionPath, 'folder', '.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(0);\n\n    updateFile(path.join(collectionPath, '.env'), 'k2=v2\\nk4=v4\\nk6=v6');\n    watcher.change(mainWindow, path.join(collectionPath, '.env'), collectionId, collectionPath);\n    expect(mainWindow.webContents.send).toHaveBeenCalledTimes(1);\n    expect(mainWindow.webContents.send).toHaveBeenCalledWith(\n      'main:addOrUpdate-dotEnvironment',\n      {\n        k2: 'v2',\n        k4: 'v4',\n        k6: 'v6',\n      },\n      '1234',\n    );\n\n    deleteDirectory(collectionPath);\n  });\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/flowtest-cli\n  - packages/flowtest-electron\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "public/index.html",
    "content": "<!-- prettier-ignore -->\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta name=\"description\" content=\"Website for running FlowTestAI application\" />\n    <link rel=\"apple-touch-icon\" href=\"%PUBLIC_URL%/logo192.png\" />\n    <!--\n      manifest.json provides metadata used when your web app is installed on a\n      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" />\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap\"\n    />\n    <title>FlowTest</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <div id=\"modal-root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"short_name\": \"FlowTest\",\n  \"name\": \"Drag & Drop UI for openAI function calling\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"logo192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "src/App.css",
    "content": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .App-logo {\n    animation: App-logo-spin infinite 20s linear;\n  }\n}\n\n.App-header {\n  background-color: #282c34;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  font-size: calc(10px + 2vmin);\n  color: white;\n}\n\n.App-link {\n  color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "src/App.js",
    "content": "import React from 'react';\nimport './App.css';\nimport Routes from './routes';\nimport { HashRouter } from 'react-router-dom';\n\nfunction App() {\n  return (\n    <HashRouter>\n      <Routes />\n    </HashRouter>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "src/App.test.js",
    "content": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () => {\n  render(<App />);\n  const linkElement = screen.getByText(/learn react/i);\n  expect(linkElement).toBeInTheDocument();\n});\n"
  },
  {
    "path": "src/components/atoms/EditableTextItem.js",
    "content": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { EditText } from 'react-edit-text';\nimport 'react-edit-text/dist/index.css';\n\nconst EditableTextItem = ({ initialText }) => {\n  // ToDo: Fix: as of now it is taking empty value which should not be the case\n  return <EditText name='editText' className='text-xl' defaultValue={initialText} placeholder={initialText} />;\n};\n\n// https://legacy.reactjs.org/docs/typechecking-with-proptypes.html\nEditableTextItem.propTypes = {\n  initialText: PropTypes.string.isRequired,\n};\n\nexport default EditableTextItem;\n"
  },
  {
    "path": "src/components/atoms/Editor.js",
    "content": "import React, { useRef, useEffect, useState } from 'react';\n\nimport { basicSetup } from 'codemirror';\nimport { EditorState, Compartment } from '@codemirror/state';\nimport { EditorView, keymap, lineNumbers, tooltips } from '@codemirror/view';\nimport { indentWithTab, history } from '@codemirror/commands';\nimport { json } from '@codemirror/lang-json';\nimport { defaultKeymap } from '@codemirror/commands';\nimport { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';\nimport { isEqual } from 'lodash';\nimport { autocompletion, CompletionContext } from '@codemirror/autocomplete';\n\n// Function to dynamically generate autocomplete options\nconst createAutocompleteSource = (options) => {\n  return (context) => {\n    let word = context.matchBefore(/\\{\\{\\w*$/);\n    if (!word) return null;\n\n    return {\n      from: word.from,\n      options: options.map((option) => ({ label: `{{${option}}}`, type: 'keyword' })),\n    };\n  };\n};\n\n// Create a Compartment for autocomplete\nconst autocompleteCompartment = new Compartment();\n\n// Function to update autocomplete options\nconst updateAutocompleteOptions = (view, newOptions) => {\n  view.dispatch({\n    effects: autocompleteCompartment.reconfigure(autocompletion({ override: [createAutocompleteSource(newOptions)] })),\n  });\n};\n\n// Custom styles to hide scrollbar\nconst hideScrollbar = EditorView.theme({\n  '.cm-scroller': {\n    overflowX: 'auto',\n    overflowY: 'hidden',\n    whiteSpace: 'nowrap',\n    scrollbarWidth: 'none' /* For Firefox */,\n  },\n  '&': {\n    cursor: 'text',\n  },\n});\n\nexport const Editor = ({ ...props }) => {\n  const editor = useRef();\n  const [view, setView] = useState(null);\n  const [dynamicOptions, setDynamicOptions] = useState(props.completionOptions || []);\n\n  if (view) {\n    if (!isEqual(dynamicOptions, props.completionOptions)) {\n      updateAutocompleteOptions(view, props.completionOptions);\n      setDynamicOptions(props.completionOptions);\n    }\n    if (props.value != view.state.doc.toString()) {\n      view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: props.value } });\n    }\n  }\n\n  const onUpdate = EditorView.updateListener.of((v) => {\n    if (props.onChange) {\n      props.onChange(v.state.doc.toString());\n    }\n  });\n\n  useEffect(() => {\n    const state = EditorState.create({\n      doc: props.value || '',\n      extensions: [\n        lineNumbers(),\n        json(),\n        basicSetup,\n        keymap.of([defaultKeymap, indentWithTab]),\n        onUpdate,\n        EditorState.readOnly.of(props.readOnly || false),\n        history(),\n        hideScrollbar,\n        autocompleteCompartment.of(autocompletion({ override: [createAutocompleteSource(dynamicOptions)] })),\n        tooltips({\n          parent: document.body,\n        }),\n      ],\n    });\n\n    const view = new EditorView({ state, parent: editor.current });\n    setView(view);\n\n    return () => {\n      view.destroy();\n      setView(null);\n    };\n  }, []);\n\n  return <div ref={editor} className={`${props.classes} overflow-auto`}></div>;\n};\n"
  },
  {
    "path": "src/components/atoms/Logo.js",
    "content": "import React from 'react';\nimport FlowTestAI from 'assets/icons/FlowTestAI.png';\n\nconst AppLogo = ({ styleClasses }) => {\n  return (\n    <div className={styleClasses}>\n      <img src={FlowTestAI} alt='FlowTestAI app logo' />\n    </div>\n  );\n};\n\nexport default AppLogo;\n"
  },
  {
    "path": "src/components/atoms/SelectAuthKeys.js",
    "content": "// ToDo: Remove as we ar not using it\n// import React, { Fragment, useState } from 'react';\n// import { Listbox, Transition } from '@headlessui/react';\n// import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\n// const authKeys = [{ name: 'Select Auth Key' }, { name: 'No Authorization' }];\n\n// const SelectAuthKeys = () => {\n//   const [selected, setSelected] = useState(authKeys[0]);\n//   return (\n//     <Listbox value={selected} onChange={setSelected}>\n//       <div className='relative rounded border border-solid border-[#94a3b8]'>\n//         <Listbox.Button className='relative w-full py-2 pl-3 pr-10 text-left bg-white rounded-lg cursor-default sm:text-sm'>\n//           <span className='block truncate'>{selected.name}</span>\n//           <span className='absolute flex items-center pr-2 pointer-events-none right-2 top-3'>\n//             <ChevronUpDownIcon className='w-5 h-5 text-gray-400' aria-hidden='true' />\n//           </span>\n//         </Listbox.Button>\n//         <Transition as={Fragment} leave='transition ease-in duration-100' leaveFrom='opacity-100' leaveTo='opacity-0'>\n//           <Listbox.Options className='absolute z-10 w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black/5 sm:text-sm'>\n//             {authKeys.map((authKey, authKeyIdx) => (\n//               <Listbox.Option\n//                 key={authKeyIdx}\n//                 className={({ active }) =>\n//                   `relative cursor-default select-none py-2 pl-10 pr-4 ${\n//                     active ? 'bg-amber-100 text-amber-900' : 'text-gray-900'\n//                   }`\n//                 }\n//                 value={authKey}\n//               >\n//                 {({ selected }) => (\n//                   <>\n//                     <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>{authKey.name}</span>\n//                     {selected ? (\n//                       <span className='absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600'>\n//                         <CheckIcon className='w-5 h-5' aria-hidden='true' />\n//                       </span>\n//                     ) : null}\n//                   </>\n//                 )}\n//               </Listbox.Option>\n//             ))}\n//           </Listbox.Options>\n//         </Transition>\n//       </div>\n//     </Listbox>\n//   );\n// };\n\n// export default SelectAuthKeys;\n"
  },
  {
    "path": "src/components/atoms/SelectEnvironment.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { Square3Stack3DIcon } from '@heroicons/react/24/outline';\nimport { useTabStore } from 'stores/TabStore';\n\nconst SelectEnvironment = ({ environments }) => {\n  const setEnv = useTabStore((state) => state.setSelectedEnv);\n\n  const [selected, setSelected] = useState(null);\n  if (selected) {\n    setEnv(selected);\n  }\n\n  return (\n    <Listbox value={selected} onChange={setSelected}>\n      <div className='relative flex h-full pl-4 border-l border-gray-300'>\n        <Listbox.Button\n          className={`flex items-center justify-between gap-2 sm:text-sm ${environments.length ? 'cursor-default' : 'cursor-not-allowed'}`}\n        >\n          <Square3Stack3DIcon className='w-4 h-4' />\n          <div className='min-w-32'>{selected ? selected : 'Select environment'}</div>\n          <ChevronUpDownIcon className='w-5 h-5' aria-hidden='true' />\n        </Listbox.Button>\n        {environments.length ? (\n          <Transition as={Fragment} leave='transition ease-in duration-100' leaveFrom='opacity-100' leaveTo='opacity-0'>\n            <Listbox.Options className='absolute right-0 z-10 w-full py-1 mt-1 overflow-auto text-base bg-white rounded shadow-lg top-12 max-h-60 ring-1 ring-black/5 sm:text-sm'>\n              {environments.map((environment, environmentIndex) => (\n                <Listbox.Option\n                  key={environmentIndex}\n                  className={({ active }) =>\n                    `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                      active ? 'bg-background-light text-slate-900' : ''\n                    }`\n                  }\n                  value={environment.name}\n                >\n                  {({ selected }) => (\n                    <>\n                      <span className={`block truncate`}>{environment.name}</span>\n                      {selected ? (\n                        <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                          <CheckIcon className='w-5 h-5' aria-hidden='true' />\n                        </span>\n                      ) : null}\n                    </>\n                  )}\n                </Listbox.Option>\n              ))}\n            </Listbox.Options>\n          </Transition>\n        ) : (\n          ''\n        )}\n      </div>\n    </Listbox>\n  );\n};\n\nSelectEnvironment.propTypes = {\n  environments: PropTypes.Array.isRequired,\n};\n\nexport default SelectEnvironment;\n"
  },
  {
    "path": "src/components/atoms/Tabs.js",
    "content": "import React, { useState } from 'react';\nimport { XMarkIcon } from '@heroicons/react/24/outline';\nimport { useTabStore } from 'stores/TabStore';\nimport ConfirmActionModal from 'components/molecules/modals/ConfirmActionModal';\nimport { isEqual } from 'lodash';\nimport { OBJ_TYPES } from 'constants/Common';\nimport { isSaveNeeded } from './util';\nimport { saveHandle } from 'components/molecules/modals/SaveFlowModal';\n\nconst tabUnsavedChanges = (tab) => {\n  if (tab.type === OBJ_TYPES.flowtest && tab.flowDataDraft) {\n    return isSaveNeeded(tab.flowData, tab.flowDataDraft);\n  } else if (tab.type === OBJ_TYPES.environment && tab.variablesDraft && !isEqual(tab.variables, tab.variablesDraft)) {\n    return true;\n  } else {\n    return false;\n  }\n};\n\nconst Tabs = () => {\n  const tabs = useTabStore((state) => state.tabs);\n  const setFocusTab = useTabStore((state) => state.setFocusTab);\n  const focusTabId = useTabStore((state) => state.focusTabId);\n  const focusTab = tabs.find((t) => t.id === focusTabId);\n  const [confirmActionModalOpen, setConfirmActionModalOpen] = useState(false);\n  const closeTab = useTabStore((state) => state.closeTab);\n  const [closingTab, setClosingTab] = useState('');\n  const [closingCollectionId, setClosingCollectionId] = useState('');\n  // ToDo: change color according to theme\n  const activeTabStyles = 'bg-cyan-900 text-white';\n  const tabCommonStyles =\n    'tab flex items-center gap-x-2 border-r border-neutral-300 pr-0 tracking-[0.15em] transition duration-300 ease-in text-sm flex-nowrap';\n  const messageForConfirmActionModal = `You have unsaved changes in the ${focusTab?.type}, are you sure you want to close it?`;\n\n  const handleCloseTab = (event, tab) => {\n    event.stopPropagation();\n    event.preventDefault();\n\n    setClosingTab(tab);\n    setClosingCollectionId(tab.collectionId);\n\n    if (tabUnsavedChanges(tab)) {\n      console.debug(`Confirm close for tabId: ${tab.id} : collectionId: ${tab.collectionId}`);\n      setConfirmActionModalOpen(true);\n      return;\n    }\n    closeTab(tab.id, tab.collectionId);\n  };\n\n  return (\n    <div role='tablist' className='tabs tabs-lg'>\n      {tabs\n        // tabs belonging to one collection will be shown at a time\n        //.reverse()\n        .filter((t) => t.collectionId === focusTab.collectionId)\n        .map((tab, index) => {\n          return (\n            <div\n              key={index}\n              className={`${tabCommonStyles} ${focusTabId === tab.id ? activeTabStyles : ''}`}\n              role='tab'\n              onClick={() => {\n                setFocusTab(tab.id);\n                console.debug(`Selected tab: ${tab.id}`);\n              }}\n              data-id={tab.id}\n              data-collection-id={tab.collectionId}\n            >\n              <a className='text-nowrap'>\n                {tabUnsavedChanges(tab) ? '*' : ''}\n                {tab.name}\n              </a>\n              <div\n                // ToDo: change color according to theme\n                className={`flex h-full items-center px-2 ${focusTabId === tab.id ? 'text-white hover:bg-cyan-950 ' : 'text-cyan-900 hover:bg-slate-200'} `}\n                data-tab-id={tab.id}\n                onClick={(e) => handleCloseTab(e, tab)}\n              >\n                <XMarkIcon className='w-4 h-4' />\n              </div>\n            </div>\n          );\n        })}\n      <ConfirmActionModal\n        closeFn={() => {\n          closeTab(closingTab.id, closingCollectionId);\n          setConfirmActionModalOpen(false);\n        }}\n        open={confirmActionModalOpen}\n        message={messageForConfirmActionModal}\n        actionFn={() => {\n          saveHandle(closingTab);\n          closeTab(closingTab.id, closingCollectionId);\n          setConfirmActionModalOpen(false);\n        }}\n        closeModal={() => setConfirmActionModalOpen(false)}\n        leftButtonMessage={'Close Withuout Saving'}\n        rightButtonMessage={'Save And Close'}\n      />\n    </div>\n  );\n};\n\nexport default Tabs;\n"
  },
  {
    "path": "src/components/atoms/ThemeController.js",
    "content": "import React from 'react';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\n\nconst ThemeController = () => {\n  const [dark, setDark] = React.useState(false);\n  const darkModeHandler = () => {\n    setDark(!dark);\n    document.body.classList.toggle('dark');\n  };\n\n  return (\n    // <Tippy content='Coming Soon!' placement='bottom'>\n    //   <label className='flex items-center gap-2 cursor-pointer'>\n    //     <svg\n    //       xmlns='http://www.w3.org/2000/svg'\n    //       width='16'\n    //       height='16'\n    //       viewBox='0 0 24 24'\n    //       fill='none'\n    //       stroke='currentColor'\n    //       strokeWidth='2'\n    //       strokeLinecap='round'\n    //       strokeLinejoin='round'\n    //     >\n    //       <circle cx='12' cy='12' r='5' />\n    //       <path d='M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4' />\n    //     </svg>\n    //     <input type='checkbox' value='synthwave' className='theme-controller toggle toggle-xs' disabled />\n    //     <svg\n    //       xmlns='http://www.w3.org/2000/svg'\n    //       width='16'\n    //       height='16'\n    //       viewBox='0 0 24 24'\n    //       fill='none'\n    //       stroke='currentColor'\n    //       strokeWidth='2'\n    //       strokeLinecap='round'\n    //       strokeLinejoin='round'\n    //     >\n    //       <path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z'></path>\n    //     </svg>\n    //   </label>\n    // </Tippy>\n    <div className=''>\n      <button onClick={() => darkModeHandler()}>\n        {dark && (\n          <>\n            <svg\n              xmlns='http://www.w3.org/2000/svg'\n              width='16'\n              height='16'\n              viewBox='0 0 24 24'\n              fill='none'\n              stroke='currentColor'\n              strokeWidth='2'\n              strokeLinecap='round'\n              strokeLinejoin='round'\n            >\n              <circle cx='12' cy='12' r='5' />\n              <path d='M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4' />\n            </svg>\n          </>\n        )}\n        {!dark && (\n          <>\n            <svg\n              xmlns='http://www.w3.org/2000/svg'\n              width='16'\n              height='16'\n              viewBox='0 0 24 24'\n              fill='none'\n              stroke='currentColor'\n              strokeWidth='2'\n              strokeLinecap='round'\n              strokeLinejoin='round'\n            >\n              <path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z'></path>\n            </svg>\n          </>\n        )}\n      </button>\n    </div>\n  );\n};\n\nexport default ThemeController;\n"
  },
  {
    "path": "src/components/atoms/common/Button.js",
    "content": "import React from 'react';\nimport { PropTypes } from 'prop-types';\n\n// ToDo: can be more generalized\nconst Button = ({\n  children,\n  classes,\n  btnType,\n  intentType,\n  isDisabled,\n  onClickHandle,\n  fullWidth,\n  onlyIcon,\n  padding,\n}) => {\n  const btnColorStyles = {\n    primary: {\n      default: 'border-cyan-900 text-white bg-cyan-900 hover:border-cyan-950 hover:bg-cyan-950', // with bg color\n      intent: {\n        info: 'border-sky-600 bg-sky-600 text-white hover:bg-sky-700',\n        success: 'border-green-600 bg-green-600 text-white hover:bg-green-700',\n        warning: 'border-amber-600 bg-amber-600 text-white hover:bg-amber-700',\n        error: 'border-red-600 bg-red-600 text-white hover:bg-red-700',\n      },\n    },\n    secondary: {\n      default: 'border-cyan-900 text-cyan-900 bg-background-light hover:bg-background border', // outline\n      intent: {\n        info: 'border border-sky-600 bg-sky-50 text-sky-600 hover:bg-sky-100',\n        success: 'border border-green-600 bg-green-50 text-green-600 hover:bg-green-100',\n        warning: 'border border-amber-600 bg-amber-50 text-amber-600 hover:bg-amber-100',\n        error: 'border border-red-600 bg-red-50 text-red-600 hover:bg-red-100',\n      },\n    },\n    tertiary: {\n      default: 'text-cyan-900 hover:bg-background-light', // without border but with primary color scheme\n      intent: {\n        info: 'text-sky-600 hover:bg-sky-100',\n        success: 'text-green-600 hover:bg-green-100',\n        warning: 'text-amber-600 hover:bg-amber-100',\n        error: 'text-red-600 hover:bg-red-100',\n      },\n    },\n    minimal: {\n      default: 'text-cyan-950 hover:bg-background-light', // without border and a neutral button\n      intent: {\n        info: 'text-sky-600 hover:text-sky-900 hover:font-semibold',\n        success: 'text-green-600 hover:text-green-900 hover:font-semibold',\n        warning: 'text-amber-600 hover:text-amber-900 hover:font-semibold',\n        error: 'text-red-600 hover:text-red-900 hover:font-semibold',\n      },\n    },\n    disabled: {\n      default: 'text-gray-300 border border-gray-300 cursor-not-allowed',\n    },\n  };\n\n  const getButtonStyles = () => {\n    const buttonColorStyles = btnColorStyles[btnType];\n    let styles = '';\n    if (onlyIcon) {\n      if (padding && padding !== '') {\n        styles = `${commonStyles} ${padding}`;\n      } else {\n        styles = `${commonStyles} p-0.5`;\n      }\n    } else {\n      styles = `${commonStyles} px-4 py-2`;\n    }\n    if (intentType) {\n      return `${buttonColorStyles.intent[intentType]} ${styles}`;\n    }\n    return `${buttonColorStyles.default} ${styles}`;\n  };\n\n  const commonStyles = `inline-flex items-center justify-center gap-2 whitespace-nowrap outline-none rounded transition ${fullWidth ? 'w-full' : ''} ${classes ? classes : ''}`;\n  return (\n    <button className={getButtonStyles()} disabled={isDisabled} onClick={onClickHandle}>\n      {children}\n    </button>\n  );\n};\n\nButton.propTypes = {\n  children: PropTypes.node.isRequired,\n  btnType: PropTypes.string.isRequired,\n  isDisabled: PropTypes.boolean.isRequired,\n  onClickHandle: PropTypes.func,\n  fullWidth: PropTypes.boolean,\n};\nexport default Button;\n"
  },
  {
    "path": "src/components/atoms/common/HomeLoadingScreen.js",
    "content": "import React from 'react';\n// import PropTypes from 'prop-types';\nimport FlowGPU from 'assets/icons/Flow-GPU-text-no-background-white.png';\n\nconst HomeLoadingScreen = () => {\n  return (\n    <div className='absolute z-50 flex flex-col items-center justify-center w-full h-full p-4 bg-slate-500/50'>\n      <div className='h-72 w-72'>\n        <img src={FlowGPU} alt='FlowTestAI app logo' />\n      </div>\n      <span className='mt-8 text-w hite loading loading-spinner loading-lg'></span>\n    </div>\n  );\n};\n\n// HomeLoadingScreen.propTypes = {};\n\nexport default HomeLoadingScreen;\n"
  },
  {
    "path": "src/components/atoms/common/HorizontalDivider.js",
    "content": "import React from 'react';\n\nconst HorizontalDivider = ({ themeColor, themeStyles }) => {\n  return (\n    <div\n      className={`h-[1px] w-full  ${themeColor ? themeColor : 'bg-gray-300'}  ${themeStyles ? themeStyles : ''}`}\n    ></div>\n  );\n};\n\nexport default HorizontalDivider;\n"
  },
  {
    "path": "src/components/atoms/common/LoadingSpinner.js",
    "content": "import React, { useState } from 'react';\n\nconst LoadingSpinner = ({ spinnerColor }) => {\n  return (\n    <div className='absolute z-50 flex items-center justify-center w-full h-full p-4 bg-slate-500/50'>\n      <span className={`loading loading-spinner loading-lg ${spinnerColor}`}></span>\n    </div>\n  );\n};\n\nexport default LoadingSpinner;\n"
  },
  {
    "path": "src/components/atoms/common/NumberInput.js",
    "content": "import React from 'react';\n\nconst NumberInput = ({ placeHolder, onChangeHandler, name, value }) => {\n  return (\n    <input\n      type='number'\n      placeholder={placeHolder}\n      className='nodrag nowheel bg-background-light block w-full rounded border border-slate-700 p-2.5 text-sm text-slate-900 outline-none'\n      name={name}\n      onChange={onChangeHandler}\n      value={value}\n    />\n  );\n};\n\nexport default NumberInput;\n"
  },
  {
    "path": "src/components/atoms/common/TextEditor.js",
    "content": "import React, { useRef, useEffect, useState } from 'react';\n\nimport { basicSetup } from 'codemirror';\nimport { EditorState, Compartment } from '@codemirror/state';\nimport { EditorView, keymap, lineNumbers, placeholder, Decoration, ViewPlugin, MatchDecorator } from '@codemirror/view';\nimport { indentWithTab, history } from '@codemirror/commands';\n//import { json } from '@codemirror/lang-json';\nimport { defaultKeymap } from '@codemirror/commands';\nimport { syntaxHighlighting, defaultHighlightStyle, HighlightStyle, StreamLanguage } from '@codemirror/language';\nimport { autocompletion, CompletionContext } from '@codemirror/autocomplete';\nimport { tags, styleTags } from '@lezer/highlight';\nimport { isEqual } from 'lodash';\n\n// Function to dynamically generate autocomplete options\nconst createAutocompleteSource = (options) => {\n  return (context) => {\n    let word = context.matchBefore(/\\{\\{\\w*$/);\n    if (!word) return null;\n\n    return {\n      from: word.from,\n      options: options.map((option) => ({ label: `{{${option}}}`, type: 'keyword' })),\n    };\n  };\n};\n\n// Create a Compartment for autocomplete\nconst autocompleteCompartment = new Compartment();\n\n// Function to update autocomplete options\nconst updateAutocompleteOptions = (view, newOptions) => {\n  view.dispatch({\n    effects: autocompleteCompartment.reconfigure(autocompletion({ override: [createAutocompleteSource(newOptions)] })),\n  });\n};\n\n// Custom styles to hide scrollbar\nconst hideScrollbar = EditorView.theme({\n  '.cm-scroller': {\n    overflowX: 'auto',\n    overflowY: 'hidden',\n    whiteSpace: 'nowrap',\n    scrollbarWidth: 'none' /* For Firefox */,\n  },\n  '.cm-content': {\n    padding: '0', // Adjust padding to fit your needs\n    overflow: 'auto',\n    lineHeight: '1.75rem',\n    fontSize: '1.125rem',\n  },\n  '.cm-scroller::-webkit-scrollbar': {\n    display: 'none' /* For Chrome, Safari, and Opera */,\n  },\n  '.cm-line': {\n    padding: '0', // Adjust padding to fit your needs\n  },\n  '&': {\n    height: 'auto', // Adjust height to auto for single-line input\n    cursor: 'text',\n  },\n  '.cm-placeholder': {\n    color: '#aaa', // Placeholder text color\n  },\n  '&.cm-focused': { outline: 'none' },\n});\n\n// Rebind the Enter key to do nothing\nconst rebindEnterKey = keymap.of([\n  {\n    key: 'Enter',\n    run: () => true, // Return true to prevent the default action\n  },\n]);\n\n// Create a MatchDecorator to highlight {{text}} strings\nconst decorator = new MatchDecorator({\n  // Regular expression to match {{text}} strings\n  regexp: /{{[^}]*}}/g,\n  decoration: Decoration.mark({ class: 'highlight' }),\n});\n\n// View plugin to apply the MatchDecorator\nconst highlightPlugin = ViewPlugin.fromClass(\n  class {\n    constructor(view) {\n      this.decorations = decorator.createDeco(view);\n    }\n    update(update) {\n      this.decorations = decorator.updateDeco(update, this.decorations);\n    }\n  },\n  {\n    decorations: (v) => v.decorations,\n  },\n);\n\n// Custom styles for highlighting\nconst highlightStyle = EditorView.baseTheme({\n  '.highlight': {\n    color: 'brown',\n  },\n});\n\nexport const TextEditor = ({ placeHolder, onChangeHandler, value, disableState, completionOptions, styles }) => {\n  const editorRef = useRef(null);\n  const viewRef = useRef(null);\n  const [dynamicOptions, setDynamicOptions] = useState(completionOptions);\n\n  useEffect(() => {\n    if (!editorRef.current) return;\n\n    const state = EditorState.create({\n      doc: value,\n      extensions: [\n        //EditorView.lineWrapping,\n        placeholder(placeHolder),\n        //myAutocomplete,\n        //basicSetup,\n        keymap.of([defaultKeymap, indentWithTab]),\n        EditorView.updateListener.of((v) => {\n          if (onChangeHandler && v.docChanged) {\n            onChangeHandler(v.state.doc.toString());\n          }\n        }),\n        //EditorState.readOnly.of(props.readOnly || false),\n        history(),\n        hideScrollbar,\n        rebindEnterKey,\n        highlightPlugin,\n        highlightStyle,\n        autocompleteCompartment.of(autocompletion({ override: [createAutocompleteSource(dynamicOptions)] })),\n      ],\n    });\n\n    const view = new EditorView({ state, parent: editorRef.current });\n    viewRef.current = view;\n\n    return () => {\n      view.destroy();\n    };\n  }, []);\n\n  useEffect(() => {\n    const view = viewRef.current;\n    if (!view) return;\n\n    // Update completion options if they've changed\n    if (!isEqual(dynamicOptions, completionOptions)) {\n      updateAutocompleteOptions(view, completionOptions);\n      setDynamicOptions(completionOptions);\n    }\n\n    // Update content if it's different from current editor content\n    const currentContent = view.state.doc.toString();\n    if (value !== currentContent) {\n      view.dispatch({\n        changes: { from: 0, to: currentContent.length, insert: value },\n      });\n    }\n  }, [value, completionOptions]);\n\n  //const mainStyles =\n  //  'nodrag nowheel block border border-slate-700 bg-background-light p-2.5 text-base outline-none';\n  const intentStyles = disableState ? 'cursor-not-allowed text-slate-400' : 'text-slate-900';\n\n  return <div ref={editorRef} className={`${styles}`}></div>;\n};\n"
  },
  {
    "path": "src/components/atoms/common/TextInput.js",
    "content": "import React from 'react';\n\nconst TextInput = ({ id, placeHolder, onChangeHandler, name, value, disableState }) => {\n  const mainStyles =\n    'nodrag nowheel block w-full rounded border border-slate-700 bg-background-light p-2.5 text-sm outline-none';\n  const intentStyles = disableState ? 'cursor-not-allowed text-slate-400' : 'text-slate-900';\n  return (\n    <input\n      id={id}\n      type='text'\n      placeholder={placeHolder}\n      className={`${mainStyles} ${intentStyles}`}\n      name={name}\n      onChange={onChangeHandler}\n      value={value}\n      disabled={disableState}\n    />\n  );\n};\n\nexport default TextInput;\n"
  },
  {
    "path": "src/components/atoms/common/TextInputWithLabel.js",
    "content": "import React from 'react';\n\nconst TextInputWithLabel = ({ children, placeHolder, onChangeHandler, name, value, label }) => {\n  return (\n    <div className='flex items-center justify-between h-full text-sm border rounded-md bg-background-light border-slate-700 text-slate-900'>\n      <input\n        type='text'\n        placeholder={placeHolder}\n        className='nodrag nowheel block w-full bg-transparent p-2.5 text-sm outline-none'\n        name={name}\n        onChange={onChangeHandler}\n        value={value}\n      />\n      <div className='p-4 border-l rounded-tr rounded-br border-l-neutral-500'>{label}</div>\n    </div>\n  );\n};\n\nexport default TextInputWithLabel;\n"
  },
  {
    "path": "src/components/atoms/common/TimeoutSelector.js",
    "content": "import React, { useState, Fragment } from 'react';\nimport { ClockIcon } from '@heroicons/react/24/outline';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\n\nconst TimeoutSelector = ({ optionsData, onSelectHandler = () => null }) => {\n  const [selected, setSelected] = useState(null);\n\n  const getSelectedLabel = (selectedValue) => {\n    let labelToShow = '';\n    optionsData.forEach((element) => {\n      if (element.value === selectedValue) {\n        labelToShow = element.label;\n      }\n    });\n    return labelToShow;\n  };\n  return (\n    <>\n      <Listbox\n        name='timer-selector'\n        value={selected}\n        onChange={(selectedValue) => {\n          setSelected(getSelectedLabel(selectedValue));\n          onSelectHandler(selectedValue);\n        }}\n      >\n        <div className='relative flex h-full'>\n          <Listbox.Button\n            className={`flex items-center justify-between gap-1 sm:text-sm ${optionsData.length ? 'cursor-default' : 'cursor-not-allowed'}`}\n          >\n            <ClockIcon className='w-5 h-5' />\n            <div className='min-w-24'>{selected ? selected : 'Select Timeout'}</div>\n            <ChevronUpDownIcon className='w-5 h-5' aria-hidden='true' />\n          </Listbox.Button>\n          {optionsData.length ? (\n            <Transition\n              as={Fragment}\n              leave='transition ease-in duration-100'\n              leaveFrom='opacity-100'\n              leaveTo='opacity-0'\n            >\n              <Listbox.Options className='absolute right-0 z-10 w-full overflow-auto text-base bg-white rounded shadow-lg top-11 max-h-60 ring-1 ring-black/5 sm:text-sm'>\n                {optionsData.map((optionData, optionDataIndex) => (\n                  <Listbox.Option\n                    key={optionDataIndex}\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={optionData.value}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block truncate`}>{optionData.label}</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                            <CheckIcon className='w-5 h-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                ))}\n              </Listbox.Options>\n            </Transition>\n          ) : (\n            ''\n          )}\n        </div>\n      </Listbox>\n    </>\n  );\n};\n\nexport default TimeoutSelector;\n"
  },
  {
    "path": "src/components/atoms/flow/FlowNode.js",
    "content": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Handle, Position } from 'reactflow';\n\nconst FlowNode = ({ children, title, handleLeft, handleLeftData, handleRight, handleRightData }) => {\n  return (\n    <>\n      {handleLeft ? <Handle type={handleLeftData?.type} position={Position.Left} /> : ''}\n      <div\n        className={`${\n          children ? 'flex-col' : 'items-center justify-center px-6 py-4'\n        } flex rounded-md border-2 border-slate-300 bg-background-lighter`}\n      >\n        {children ? (\n          <>\n            <div className='px-4 py-2 border-b-2 border-slate-300'>\n              <h3 className='text-xl font-medium'>{title}</h3>\n            </div>\n            <div className='p-4'>{children}</div>\n          </>\n        ) : (\n          <>\n            <h3 className='text-xl font-medium'>{title}</h3>\n            {children}\n          </>\n        )}\n      </div>\n      {handleRight ? (\n        <Handle\n          type={handleRightData.type}\n          position={Position.Right}\n          id={handleRightData.id}\n          style={handleRightData.styles}\n        />\n      ) : (\n        ''\n      )}\n    </>\n  );\n};\n\nFlowNode.propTypes = {\n  children: PropTypes.node.isRequired,\n  title: PropTypes.string.isRequired,\n  handleLeft: PropTypes.node.isRequired,\n  handleLeftData: PropTypes.object.isRequired,\n  handleRight: PropTypes.node.isRequired,\n  handleRightData: PropTypes.object.isRequired,\n};\n\nexport default FlowNode;\n"
  },
  {
    "path": "src/components/atoms/flow/NodeHorizontalDivider.js",
    "content": "import React from 'react';\n\nconst NodeHorizontalDivider = () => {\n  return <div className='bg-background-dark h-[2px] w-full'></div>;\n};\n\nexport default NodeHorizontalDivider;\n"
  },
  {
    "path": "src/components/atoms/flow/Textarea.js",
    "content": "import React from 'react';\n\nconst Textarea = ({ id, placeHolder, onChangeHandler, name, value, rows }) => {\n  return (\n    <textarea\n      id={id}\n      placeholder={placeHolder}\n      className='nodrag nowheel min-h-12 w-full min-w-72 rounded border border-slate-700 bg-background-light p-2.5 text-sm text-slate-900 outline-none'\n      name={name}\n      onChange={onChangeHandler}\n      rows={rows}\n      value={value}\n    />\n  );\n};\n\nexport default Textarea;\n"
  },
  {
    "path": "src/components/atoms/sidebar/collections/OptionsMenu.js",
    "content": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Menu, Transition } from '@headlessui/react';\nimport { FolderPlusIcon, PencilSquareIcon, TrashIcon, XMarkIcon } from '@heroicons/react/24/outline';\nimport { EllipsisVerticalIcon } from '@heroicons/react/20/solid';\nimport { DirectoryOptionsActions } from 'constants/WorkspaceDirectory';\nimport { OBJ_TYPES } from 'constants/Common';\n\nconst OptionsMenu = ({ collectionId, directory, itemType }) => {\n  const menuItemsStyles =\n    'group flex w-full items-center rounded p-2 text-sm transition duration-200 ease-out hover:bg-background-light hover:font-semibold hover:font-cyan-950';\n  return (\n    <Menu\n      as='div'\n      className='relative inline-block text-left transition duration-200 ease-out rounded rounded-l-none hover:bg-slate-200'\n    >\n      <Menu.Button data-click-from='options-menu' className='p-2' data-item-type={itemType}>\n        <EllipsisVerticalIcon\n          className='w-4 h-4'\n          aria-hidden='true'\n          data-click-from='options-menu'\n          data-item-type={itemType}\n        />\n      </Menu.Button>\n      <Transition\n        as={Fragment}\n        enter='transition ease-out duration-100'\n        enterFrom='transform opacity-0 scale-95'\n        enterTo='transform opacity-100 scale-100'\n        leave='transition ease-in duration-75'\n        leaveFrom='transform opacity-100 scale-100'\n        leaveTo='transform opacity-0 scale-95'\n      >\n        <Menu.Items\n          className='absolute right-0 z-10 w-56 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black/5 focus:outline-none'\n          data-click-from='options-menu'\n          data-item-type={itemType}\n        >\n          {itemType === OBJ_TYPES.collection || itemType === OBJ_TYPES.folder ? (\n            <div className='px-1 py-1' data-click-from='options-menu' data-item-type={itemType}>\n              <Menu.Item data-click-from='options-menu' data-item-type={itemType}>\n                <button\n                  className={menuItemsStyles}\n                  data-click-from='options-menu'\n                  data-options-menu-item={DirectoryOptionsActions.addNewFolder.value}\n                  data-path-name={directory.pathname}\n                  data-item-type={itemType}\n                  data-collection-id={collectionId}\n                >\n                  <FolderPlusIcon\n                    className='w-4 h-4 mr-2'\n                    aria-hidden='true'\n                    data-click-from='options-menu'\n                    data-item-type={itemType}\n                  />\n                  {DirectoryOptionsActions.addNewFolder.displayValue}\n                </button>\n              </Menu.Item>\n              <Menu.Item data-click-from='options-menu' data-item-type={itemType}>\n                <button\n                  className={menuItemsStyles}\n                  data-click-from='options-menu'\n                  data-options-menu-item={DirectoryOptionsActions.addNewFlow.value}\n                  data-path-name={directory.pathname}\n                  data-item-type={itemType}\n                  data-collection-id={collectionId}\n                >\n                  <PencilSquareIcon\n                    className='w-4 h-4 mr-2'\n                    aria-hidden='true'\n                    data-click-from='options-menu'\n                    data-item-type={itemType}\n                  />\n                  {DirectoryOptionsActions.addNewFlow.displayValue}\n                </button>\n              </Menu.Item>\n            </div>\n          ) : (\n            <div className='px-1 py-1' data-click-from='options-menu' data-item-type={itemType}>\n              <Menu.Item data-click-from='options-menu' data-item-type={itemType}>\n                <button\n                  className={menuItemsStyles}\n                  data-click-from='options-menu'\n                  data-options-menu-item={DirectoryOptionsActions.cloneFlow.value}\n                  data-path-name={directory.pathname}\n                  data-item-type={itemType}\n                  data-collection-id={collectionId}\n                >\n                  <PencilSquareIcon\n                    className='w-4 h-4 mr-2'\n                    aria-hidden='true'\n                    data-click-from='options-menu'\n                    data-item-type={itemType}\n                  />\n                  {DirectoryOptionsActions.cloneFlow.displayValue}\n                </button>\n              </Menu.Item>\n            </div>\n          )}\n          <div className='px-1 py-1' data-click-from='options-menu' data-item-type={itemType}>\n            <Menu.Item data-click-from='options-menu' data-item-type={itemType}>\n              <button\n                className={menuItemsStyles}\n                data-click-from='options-menu'\n                data-options-menu-item={DirectoryOptionsActions.delete.value}\n                data-path-name={directory.pathname}\n                data-item-type={itemType}\n                data-collection-id={collectionId}\n              >\n                {itemType === OBJ_TYPES.collection ? (\n                  <>\n                    <XMarkIcon\n                      className='w-4 h-4 mr-2'\n                      aria-hidden='true'\n                      data-click-from='options-menu'\n                      data-item-type={itemType}\n                    />\n                    {DirectoryOptionsActions.remove.displayValue}\n                  </>\n                ) : (\n                  <>\n                    <TrashIcon\n                      className='w-4 h-4 mr-2'\n                      aria-hidden='true'\n                      data-click-from='options-menu'\n                      data-item-type={itemType}\n                    />\n                    {DirectoryOptionsActions.delete.displayValue}\n                  </>\n                )}\n              </button>\n            </Menu.Item>\n          </div>\n        </Menu.Items>\n      </Transition>\n    </Menu>\n  );\n};\n\nOptionsMenu.propTypes = {\n  directory: PropTypes.object.isRequired,\n  itemType: PropTypes.string.isRequired,\n};\n\nexport default OptionsMenu;\n"
  },
  {
    "path": "src/components/atoms/sidebar/environments/EnvOptionsMenu.js",
    "content": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Menu, Transition } from '@headlessui/react';\nimport { PencilSquareIcon } from '@heroicons/react/24/outline';\nimport { EllipsisVerticalIcon } from '@heroicons/react/20/solid';\nimport { DirectoryOptionsActions } from 'constants/WorkspaceDirectory';\n\n// ToDo: Combine this component with OptionsMenu in sidebar atoms to make it generic\nconst EnvOptionsMenu = ({ pathName, itemType, collectionId }) => {\n  const menuItemsStyles =\n    'group flex w-full items-center rounded px-2 py-2 text-sm text-gray-900 transition duration-200 ease-out hover:bg-background-light';\n  return (\n    <Menu\n      as='div'\n      className='relative inline-block text-left transition duration-200 ease-out rounded rounded-l-none hover:bg-slate-200'\n    >\n      <Menu.Button data-click-from='env-options-menu' className='p-2' data-item-type={itemType}>\n        <EllipsisVerticalIcon\n          className='w-4 h-4'\n          aria-hidden='true'\n          data-click-from='env-options-menu'\n          data-item-type={itemType}\n        />\n      </Menu.Button>\n      <Transition\n        as={Fragment}\n        enter='transition ease-out duration-100'\n        enterFrom='transform opacity-0 scale-95'\n        enterTo='transform opacity-100 scale-100'\n        leave='transition ease-in duration-75'\n        leaveFrom='transform opacity-100 scale-100'\n        leaveTo='transform opacity-0 scale-95'\n      >\n        <Menu.Items\n          className='absolute right-0 z-10 w-56 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black/5 focus:outline-none'\n          data-click-from='env-options-menu'\n          data-item-type={itemType}\n        >\n          <div className='px-1 py-1' data-click-from='env-options-menu' data-item-type={itemType}>\n            <Menu.Item data-click-from='env-options-menu' data-item-type={itemType}>\n              <button\n                className={menuItemsStyles}\n                data-click-from='env-options-menu'\n                data-options-menu-item={DirectoryOptionsActions.addNewEnvironment.value}\n                data-path-name={pathName}\n                data-collection-id={collectionId}\n              >\n                <PencilSquareIcon\n                  className='w-4 h-4 mr-2'\n                  aria-hidden='true'\n                  data-click-from='env-options-menu'\n                  data-item-type={itemType}\n                />\n                New Environment\n              </button>\n            </Menu.Item>\n          </div>\n        </Menu.Items>\n      </Transition>\n    </Menu>\n  );\n};\n\nEnvOptionsMenu.propTypes = {\n  pathName: PropTypes.string.isRequired,\n  itemType: PropTypes.string.isRequired,\n};\n\nexport default EnvOptionsMenu;\n"
  },
  {
    "path": "src/components/atoms/util.js",
    "content": "import _, { isEqual, map } from 'lodash';\n\nexport const compare = (a, b) => {\n  const result = {\n    different: [],\n    missing_from_first: [],\n    missing_from_second: [],\n  };\n\n  _.reduce(\n    a,\n    function (result, value, key) {\n      if (Object.prototype.hasOwnProperty.call(b, key)) {\n        if (isEqual(value, b[key])) {\n          return result;\n        } else {\n          if (typeof a[key] != typeof {} || typeof b[key] != typeof {}) {\n            //dead end.\n            result.different.push(key);\n            return result;\n          } else {\n            var deeper = compare(a[key], b[key]);\n            result.different = result.different.concat(\n              map(deeper.different, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n\n            result.missing_from_second = result.missing_from_second.concat(\n              map(deeper.missing_from_second, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n\n            result.missing_from_first = result.missing_from_first.concat(\n              map(deeper.missing_from_first, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n            return result;\n          }\n        }\n      } else {\n        result.missing_from_second.push(key);\n        return result;\n      }\n    },\n    result,\n  );\n\n  _.reduce(\n    b,\n    function (result, value, key) {\n      if (Object.prototype.hasOwnProperty.call(a, key)) {\n        return result;\n      } else {\n        result.missing_from_first.push(key);\n        return result;\n      }\n    },\n    result,\n  );\n\n  return result;\n};\n\nexport const isSaveNeeded = (flowData, flowDataDraft) => {\n  if (flowData.nodes.length != flowDataDraft.nodes.length) {\n    console.log('Detected unsaved changes: node count');\n    return true;\n  }\n\n  if (flowData.edges.length != flowDataDraft.edges.length) {\n    console.log('Detected unsaved changes: edges count');\n    return true;\n  }\n\n  const unwrapEdge = function ({ source, sourceHandle, target, targetHandle }) {\n    return { source, sourceHandle, target, targetHandle };\n  };\n\n  const edges = flowData.edges.map((e) => unwrapEdge(e));\n  const edgesDraft = flowDataDraft.edges.map((e) => unwrapEdge(e));\n\n  if (!isEqual(edges, edgesDraft)) {\n    console.log('Detected unsaved changes: ', compare(edges, edgesDraft));\n    return true;\n  }\n\n  const unwrapNode = function ({ id, type, data, position }) {\n    return { id, type, data, position };\n  };\n\n  const nodes = flowData.nodes.map((node) => {\n    if (node.type === 'outputNode' && node.data.output) {\n      const { ['output']: _, ...data } = node.data;\n      return unwrapNode({\n        ...node,\n        data,\n      });\n    }\n    return unwrapNode(node);\n  });\n  const nodesDraft = flowDataDraft.nodes.map((node) => {\n    if (node.type === 'outputNode' && node.data.output) {\n      const { ['output']: _, ...data } = node.data;\n      return unwrapNode({\n        ...node,\n        data,\n      });\n    }\n    return unwrapNode(node);\n  });\n\n  if (!isEqual(nodes, nodesDraft)) {\n    console.log('Detected unsaved changes: ', compare(nodes, nodesDraft));\n    return true;\n  }\n\n  return false;\n};\n"
  },
  {
    "path": "src/components/layouts/SplitPane.js",
    "content": "import React from 'react';\nimport { Allotment } from 'allotment';\nimport 'allotment/dist/style.css';\nimport Workspace from '../organisms/workspace/Workspace';\nimport AppNavBar from 'components/organisms/AppNavBar';\nimport SideBar from 'components/organisms/SideBar';\nimport useNavigationStore from 'stores/AppNavBarStore';\nimport { AppNavBarStyles } from 'constants/AppNavBar';\n\nconst SplitPane = () => {\n  const isNavBarCollapsed = useNavigationStore((state) => state.collapseNavBar);\n  return (\n    <main className='h-full'>\n      <Allotment>\n        <Allotment.Pane\n          preferredSize={isNavBarCollapsed ? AppNavBarStyles.collapsedNavBarWidth.absolute : 450}\n          minSize={isNavBarCollapsed ? AppNavBarStyles.collapsedNavBarWidth.absolute : 450}\n          maxSize={isNavBarCollapsed ? AppNavBarStyles.collapsedNavBarWidth.absolute : 600}\n          separator={isNavBarCollapsed ? false : true}\n        >\n          <div className='flex h-full text-xs'>\n            <AppNavBar />\n            {!isNavBarCollapsed ? (\n              <div className='w-full h-full'>\n                <Allotment>\n                  <Allotment.Pane>\n                    <SideBar />\n                  </Allotment.Pane>\n                </Allotment>\n              </div>\n            ) : (\n              ''\n            )}\n          </div>\n        </Allotment.Pane>\n        <Allotment.Pane>\n          <Workspace />\n        </Allotment.Pane>\n      </Allotment>\n    </main>\n  );\n};\n\nexport default SplitPane;\n"
  },
  {
    "path": "src/components/layouts/WithoutSplitPane.js",
    "content": "import React from 'react';\nimport { Allotment } from 'allotment';\nimport 'allotment/dist/style.css';\nimport Workspace from '../organisms/workspace/Workspace';\nimport AppNavBar from 'components/organisms/AppNavBar';\n\nconst WithoutSplitPane = () => {\n  const sideBarSize = 112;\n  return (\n    <main className='h-full'>\n      <Allotment>\n        <Allotment.Pane preferredSize={`${sideBarSize}px`} minSize={sideBarSize} maxSize={sideBarSize}>\n          <div className='flex text-xs'>\n            <AppNavBar showRightBorder={false} />\n          </div>\n        </Allotment.Pane>\n        <Allotment.Pane>\n          <Workspace />\n        </Allotment.Pane>\n      </Allotment>\n    </main>\n  );\n};\n\nexport default WithoutSplitPane;\n"
  },
  {
    "path": "src/components/molecules/environment/index.js",
    "content": "import React, { useState } from 'react';\nimport useEnvStore from 'stores/EnvStore';\nimport 'react-edit-text/dist/index.css';\nimport { TrashIcon, PlusIcon, PencilSquareIcon } from '@heroicons/react/24/outline';\nimport AddEnvVariableModal from '../modals/AddEnvVariableModal';\nimport ConfirmActionModal from '../modals/ConfirmActionModal';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES, OBJ_TYPES } from 'constants/Common';\nimport EditEnvVariableModal from '../modals/EditEnvVariableModal';\nimport { useKeyPress } from 'reactflow';\nimport { saveHandle } from '../modals/SaveFlowModal';\nimport Mousetrap from 'mousetrap';\n\nconst Env = ({ tab }) => {\n  const variables = useEnvStore((state) => state.variables);\n  const handleAddVariable = useEnvStore((state) => state.handleAddVariable);\n  const handleDeleteVariable = useEnvStore((state) => state.handleDeleteVariable);\n\n  const [addVariableModalOpen, setAddVariableModalOpen] = useState(false);\n  const [editVariableModalOpen, setEditVariableModalOpen] = useState(false);\n  const [confirmActionModalOpen, setConfirmActionModalOpen] = useState(false);\n\n  const [deleteKey, setDeleteKey] = useState('');\n  const [editKey, setEditKey] = useState('');\n  const [editValue, setEditValue] = useState('');\n\n  //const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);\n  // Bind Ctrl+S and Cmd+S\n  Mousetrap.bind(['ctrl+s', 'command+s'], function (e) {\n    e.preventDefault();\n    saveHandle(tab);\n    return false;\n  });\n\n  return (\n    <div className='p-4' key={tab.id}>\n      <table className='w-full table-fixed leading-normal'>\n        <thead>\n          <tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold uppercase tracking-wider'>\n            <th className='border-ghost-200 border p-5' style={{ width: '50px' }}>\n              S. No.\n            </th>\n            <th className='border-ghost-200 border p-5' style={{ width: '100px' }}>\n              Key\n            </th>\n            <th className='border-ghost-200 border p-5' style={{ width: '200px' }}>\n              Value\n            </th>\n            <th className='border-ghost-200 border p-5' style={{ width: '50px' }}>\n              Action\n            </th>\n          </tr>\n        </thead>\n        <tbody>\n          {Object.entries(variables).map(([key, value], index) => (\n            <tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>\n              <td className='whitespace-no-wrap p-5' style={{ width: '50px' }}>\n                {index + 1}\n              </td>\n              <td className='whitespace-no-wrap truncate p-5' style={{ width: '100px' }}>\n                {key}\n              </td>\n              <td className='whitespace-no-wrap truncate p-5' style={{ width: '200px' }}>\n                {value}\n              </td>\n              <td className='whitespace-no-wrap p-5' style={{ width: '50px' }}>\n                <div\n                  className='hover:bg-ghost-200 relative inline-block cursor-pointer rounded-md p-2 text-left transition duration-200 ease-out'\n                  onClick={() => {\n                    setDeleteKey(key);\n                    setConfirmActionModalOpen(true);\n                  }}\n                >\n                  <TrashIcon className='h-4 w-4' aria-hidden='true' />\n                </div>\n                <div\n                  className='hover:bg-ghost-200 relative inline-block cursor-pointer rounded-md p-2 text-left transition duration-200 ease-out'\n                  onClick={() => {\n                    setEditKey(key);\n                    setEditValue(value);\n                    setEditVariableModalOpen(true);\n                  }}\n                >\n                  <PencilSquareIcon className='h-4 w-4' aria-hidden='true' />\n                </div>\n              </td>\n            </tr>\n          ))}\n        </tbody>\n      </table>\n      <div className='mt-6'>\n        <Button\n          btnType={BUTTON_TYPES.primary}\n          isDisabled={false}\n          onClickHandle={() => setAddVariableModalOpen(true)}\n          fullWidth={true}\n        >\n          <PlusIcon className='h-5 w-5' />\n          <span>Add Variable</span>\n        </Button>\n      </div>\n      <AddEnvVariableModal\n        closeFn={() => setAddVariableModalOpen(false)}\n        open={addVariableModalOpen}\n        handleAddVariable={(key, value) => handleAddVariable(key, value)}\n      />\n      <EditEnvVariableModal\n        closeFn={() => setEditVariableModalOpen(false)}\n        open={editVariableModalOpen}\n        editKey={editKey}\n        editValue={editValue}\n        handleAddVariable={(key, value) => handleAddVariable(key, value)}\n      />\n      <ConfirmActionModal\n        closeFn={() => setConfirmActionModalOpen(false)}\n        open={confirmActionModalOpen}\n        message='Do you want to delete this variable?'\n        actionFn={() => {\n          handleDeleteVariable(deleteKey);\n          setConfirmActionModalOpen(false);\n        }}\n      />\n    </div>\n  );\n};\n\nexport default Env;\n"
  },
  {
    "path": "src/components/molecules/flow/AddNodes.js",
    "content": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Popover, Transition } from '@headlessui/react';\nimport { PlusIcon, MinusIcon } from '@heroicons/react/20/solid';\nimport { Fragment } from 'react';\nimport { Disclosure } from '@headlessui/react';\nimport { ChevronDownIcon } from '@heroicons/react/20/solid';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { orderNodesByTags } from './utils';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\nimport requestNodes from './constants/requestNodes';\n\nconst outputNode = {\n  description: 'Displays any data received.',\n  type: 'outputNode',\n};\n\nconst assertNode = {\n  description: 'Assert on conditional expressions.',\n  type: 'assertNode',\n};\n\nconst delayNode = {\n  description: 'Add a certain delay before next computation.',\n  type: 'delayNode',\n};\n\nconst authNode = {\n  description: 'Define authentication for the requests',\n  type: 'authNode',\n};\n\nconst flowNode = {\n  description: 'Helps to create nested flows',\n  type: 'flowNode',\n};\n\nconst setVarNode = {\n  description: 'Assign a value to a variable',\n  type: 'setVarNode',\n};\n\nconst AddNodes = ({ collectionId }) => {\n  const [searchFilter, setSearchFilter] = useState('');\n\n  const onDragStart = (event, node) => {\n    event.dataTransfer.setData('application/reactflow', JSON.stringify(node));\n    event.dataTransfer.effectAllowed = 'move';\n  };\n\n  // Get all requests of this collections\n  const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n  const nodesByTags = orderNodesByTags(collection.nodes, searchFilter);\n\n  return (\n    <div className='absolute bottom-4 right-4 z-[2000]'>\n      <Popover className='relative'>\n        {({ open }) => (\n          <>\n            <Popover.Button className='group inline-flex items-center rounded border-cyan-900 bg-cyan-900 px-4 py-2.5 text-base font-medium text-white hover:border-cyan-950 hover:bg-cyan-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75'>\n              {open ? (\n                <MinusIcon className='w-6 h-6 transition duration-300 ease-in-out' aria-hidden='true' />\n              ) : (\n                <PlusIcon className='w-6 h-6 transition duration-300 ease-in-out' aria-hidden='true' />\n              )}\n            </Popover.Button>\n            <Transition\n              as={Fragment}\n              enter='transition ease-out duration-200'\n              enterFrom='opacity-0 translate-y-1'\n              enterTo='opacity-100 translate-y-0'\n              leave='transition ease-in duration-150'\n              leaveFrom='opacity-100 translate-y-0'\n              leaveTo='opacity-0 translate-y-1'\n            >\n              <Popover.Panel className='absolute bottom-full w-screen max-w-sm translate-x-[-90%] transform rounded-lg bg-white shadow-2xl'>\n                <>\n                  <div className='mx-auto max-h-[60vh] w-full max-w-md overflow-scroll rounded-2xl p-2'>\n                    <h3 className='py-4 text-lg font-semibold text-center border-b '>Add Nodes</h3>\n                    <HorizontalDivider />\n                    <div className='py-4'>\n                      {/* Requests */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Requests</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5`} />\n                            </Disclosure.Button>\n\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              {requestNodes.map((node, index) => (\n                                <div\n                                  key={`${node.requestType}-${index}`}\n                                  onDragStart={(event) => onDragStart(event, node)}\n                                  draggable\n                                  cursor='move'\n                                  className='py-2 border-b'\n                                >\n                                  <div className='text-base font-semibold primary-text'>{node.requestType}</div>\n                                  <div className='text-xs secondary-text'>{node.description}</div>\n                                </div>\n                              ))}\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Saved Collections */}\n                      {collection && (\n                        <Disclosure as='div' key='collection.id'>\n                          {({ open }) => (\n                            <>\n                              <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                                <span>{collection.name}</span>\n                                <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                              </Disclosure.Button>\n                              <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                                <div>\n                                  <div className='flex items-center justify-between text-sm border rounded-md border-neutral-500 text-neutral-500 outline-0 focus:ring-0'>\n                                    <input\n                                      type='text'\n                                      className='nodrag nowheel block h-9 w-full min-w-40 rounded-bl-md rounded-tl-md p-2.5'\n                                      name='search-nodes'\n                                      placeholder='Search Nodes'\n                                      onChange={(e) => setSearchFilter(e.target.value)}\n                                      value={searchFilter}\n                                    />\n                                  </div>\n                                  <HorizontalDivider />\n                                  {Object.entries(nodesByTags).map(([tag, nodes], index) => (\n                                    <Disclosure as='div' key={index}>\n                                      {({ open }) => (\n                                        <>\n                                          <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                                            <span>{tag}</span>\n                                            <ChevronDownIcon\n                                              className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `}\n                                            />\n                                          </Disclosure.Button>\n                                          <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                                            <div>\n                                              {nodes.map((node) => (\n                                                <div\n                                                  key={`${node.requestType} - ${node.operationId}`}\n                                                  onDragStart={(event) => {\n                                                    const newNode = {\n                                                      ...node,\n                                                      type: 'requestNode',\n                                                    };\n                                                    onDragStart(event, newNode);\n                                                  }}\n                                                  draggable\n                                                  cursor='move'\n                                                  className='py-2 border-b'\n                                                >\n                                                  <div className='text-base font-semibold primary-text'>{`${node.requestType} - ${node.operationId}`}</div>\n                                                  <div className='text-xs secondary-text'>{node.description}</div>\n                                                </div>\n                                              ))}\n                                            </div>\n                                          </Disclosure.Panel>\n                                        </>\n                                      )}\n                                    </Disclosure>\n                                  ))}\n                                </div>\n                              </Disclosure.Panel>\n                            </>\n                          )}\n                        </Disclosure>\n                      )}\n                      {/* Output */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Output</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='output'\n                                onDragStart={(event) => onDragStart(event, outputNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Output</div>\n                                <div className='text-xs secondary-text'>{outputNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Assert */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Assert</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='assert'\n                                onDragStart={(event) => onDragStart(event, assertNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Assert</div>\n                                <div className='text-xs secondary-text'>{assertNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Delay */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Delay</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='delay'\n                                onDragStart={(event) => onDragStart(event, delayNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Delay</div>\n                                <div className='text-xs secondary-text'>{delayNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Authentication */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Authentication</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='auth'\n                                onDragStart={(event) => onDragStart(event, authNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Auth</div>\n                                <div className='text-xs secondary-text'>{authNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Complex */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Flow</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='complex'\n                                onDragStart={(event) => onDragStart(event, flowNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Nested Flow</div>\n                                <div className='text-xs secondary-text'>{flowNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                      {/* Set Variable */}\n                      <Disclosure as='div'>\n                        {({ open }) => (\n                          <>\n                            <Disclosure.Button className='flex justify-between w-full px-4 py-2 text-lg font-medium text-left border-t border-b bg-gray-50 hover:bg-gray-100 focus:outline-none focus-visible:ring'>\n                              <span>Set Variable</span>\n                              <ChevronDownIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5 `} />\n                            </Disclosure.Button>\n                            <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>\n                              <div\n                                key='complex'\n                                onDragStart={(event) => onDragStart(event, setVarNode)}\n                                draggable\n                                cursor='move'\n                                className='py-2 border-b'\n                              >\n                                <div className='text-base font-semibold primary-text'>Set Variable</div>\n                                <div className='text-xs secondary-text'>{setVarNode.description}</div>\n                              </div>\n                            </Disclosure.Panel>\n                          </>\n                        )}\n                      </Disclosure>\n                    </div>\n                  </div>\n                </>\n              </Popover.Panel>\n            </Transition>\n          </>\n        )}\n      </Popover>\n    </div>\n  );\n};\n\nAddNodes.propTypes = {\n  collectionId: PropTypes.string.isRequired,\n};\n\nexport default AddNodes;\n"
  },
  {
    "path": "src/components/molecules/flow/constants/assertOperators.js",
    "content": "const AssertOperators = {\n  isLessThan: 'isLessThan',\n  isGreaterThan: 'isGreaterThan',\n  isEqualTo: 'isEqualTo',\n  isNotEqualTo: 'isNotEqualTo',\n};\n\nexport default AssertOperators;\n"
  },
  {
    "path": "src/components/molecules/flow/constants/evaluateOperators.js",
    "content": "const EvaluateOperators = {\n  Add: 'Add two numbers',\n  Subtract: 'Subtract two numbers',\n};\n\nexport default EvaluateOperators;\n"
  },
  {
    "path": "src/components/molecules/flow/constants/requestNodes.js",
    "content": "const requestNodes = [\n  {\n    requestType: 'GET',\n    description: 'GET is used to request data from a specified resource.',\n    type: 'requestNode',\n  },\n  {\n    requestType: 'POST',\n    description: 'POST is used to send data to a server to create/update a resource.',\n    type: 'requestNode',\n  },\n  {\n    requestType: 'PUT',\n    description: 'PUT is used to send data to a server to create/update a resource. PUT requests are idempotent.',\n    type: 'requestNode',\n  },\n  {\n    requestType: 'DELETE',\n    description: 'DELETE is used to delete the specified resource.',\n    type: 'requestNode',\n  },\n  {\n    requestType: 'PATCH',\n    description: 'PATCH is used for making partial changes to an existing resource.',\n    type: 'requestNode',\n  },\n];\n\nexport default requestNodes;\n"
  },
  {
    "path": "src/components/molecules/flow/edges/ButtonEdge.js",
    "content": "import React from 'react';\nimport { BaseEdge, EdgeLabelRenderer, getSmoothStepPath } from 'reactflow';\nimport useCanvasStore from 'stores/CanvasStore';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport { XMarkIcon } from '@heroicons/react/24/outline';\n\n// eslint-disable-next-line react/prop-types\nexport default function CustomEdge({\n  id,\n  sourceX,\n  sourceY,\n  targetX,\n  targetY,\n  sourcePosition,\n  targetPosition,\n  style = {},\n  markerEnd,\n}) {\n  const setEdges = useCanvasStore((state) => state.setEdges);\n\n  const [edgePath, labelX, labelY] = getSmoothStepPath({\n    sourceX,\n    sourceY,\n    sourcePosition,\n    targetX,\n    targetY,\n    targetPosition,\n  });\n\n  return (\n    <>\n      <BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />\n      <EdgeLabelRenderer>\n        <div\n          style={{\n            position: 'absolute',\n            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n            fontSize: 12,\n            // everything inside EdgeLabelRenderer has no pointer events by default\n            // if you have an interactive element, set pointer-events: all\n            pointerEvents: 'all',\n          }}\n          className='nodrag nopan'\n        >\n          <Button\n            btnType={BUTTON_TYPES.secondary}\n            intentType={BUTTON_INTENT_TYPES.error}\n            classes={'rounded-full'}\n            isDisabled={false}\n            onlyIcon={true}\n            onClickHandle={(evt) => {\n              evt.stopPropagation();\n              // alert(`remove ${id}`);\n              setEdges(useCanvasStore.getState().edges.filter((e) => e.id !== id));\n            }}\n            fullWidth={false}\n          >\n            <XMarkIcon className='w-3 h-3' />\n          </Button>\n        </div>\n      </EdgeLabelRenderer>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/molecules/flow/flowtestai.js",
    "content": "import { GENAI_MODELS } from 'constants/Common';\nimport { addOrUpdateDotEnvironmentFile } from 'service/collection';\nimport useCollectionStore from 'stores/CollectionStore';\n\nconst translateGeneratedNodesToOpenApiNodes = (generatedNodes, openApiNodes) => {\n  let outputNodes = [];\n  generatedNodes.forEach((gnode, _) => {\n    const node = openApiNodes.find((node) => node.operationId === gnode.name);\n    if (node !== undefined) {\n      // take best of both worlds (parameters and request body generated by ai model and ones intelligently generated by parser)\n      const outputNode = JSON.parse(JSON.stringify(node)); //{ ...node };\n      if (gnode.arguments) {\n        const node_arguments = JSON.parse(gnode.arguments);\n        // if (node_arguments.requestBody) {\n        //   outputNode['requestBody'] = {};\n        //   outputNode['requestBody']['type'] = 'raw-json';\n        //   outputNode['requestBody']['body'] = JSON.stringify(node_arguments.requestBody);\n        // }\n        if (node_arguments.parameters) {\n          Object.entries(node_arguments.parameters).forEach(([paramName, paramValue], _) => {\n            if (!outputNode.preReqVars) {\n              outputNode.preReqVars = {};\n            }\n            outputNode.preReqVars[paramName] = {\n              type: typeof paramValue,\n              value: paramValue,\n            };\n          });\n        }\n      }\n      outputNodes.push({\n        ...outputNode,\n        type: 'requestNode',\n      });\n    } else {\n      console.log(`Cannot find node: ${gnode.name} in openApi spec`);\n    }\n  });\n\n  return outputNodes;\n};\n\nexport const generateFlowData = async (instruction, modelName, modelKey, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      if (modelName === GENAI_MODELS.openai) {\n        if (!collection.dotEnvVariables) {\n          await ipcRenderer.invoke('renderer:create-dotenv', collection.pathname, `OPENAI_APIKEY=${modelKey}`);\n        } else if (\n          !Object.prototype.hasOwnProperty.call(collection.dotEnvVariables, 'OPENAI_APIKEY') ||\n          modelKey != collection.dotEnvVariables['OPENAI_APIKEY']\n        ) {\n          await addOrUpdateDotEnvironmentFile(collectionId, {\n            ...collection.dotEnvVariables,\n            OPENAI_APIKEY: modelKey,\n          });\n        }\n        const generatedNodes = await ipcRenderer.invoke('renderer:generate-nodes-ai', instruction, collectionId, {\n          name: GENAI_MODELS.openai,\n          apiKey: modelKey,\n        });\n        const flowData = {\n          nodes: translateGeneratedNodesToOpenApiNodes(generatedNodes, collection.nodes),\n        };\n        return flowData;\n      } else if (modelName === GENAI_MODELS.bedrock_claude) {\n        if (!collection.dotEnvVariables) {\n          await ipcRenderer.invoke(\n            'renderer:create-dotenv',\n            collection.pathname,\n            `BEDROCK_ACCESS_KEYID=${modelKey.accessKeyId}\\nBEDROCK_SECRET_ACCESSKEY=${modelKey.secretAccessKey}`,\n          );\n        } else if (\n          !Object.prototype.hasOwnProperty.call(collection.dotEnvVariables, 'BEDROCK_ACCESS_KEYID') ||\n          modelKey.accessKeyId != collection.dotEnvVariables['BEDROCK_ACCESS_KEYID'] ||\n          !Object.prototype.hasOwnProperty.call(collection.dotEnvVariables, 'BEDROCK_SECRET_ACCESSKEY') ||\n          modelKey.secretAccessKey != collection.dotEnvVariables['BEDROCK_SECRET_ACCESSKEY']\n        ) {\n          const vars = {\n            BEDROCK_ACCESS_KEYID: modelKey.accessKeyId,\n            BEDROCK_SECRET_ACCESSKEY: modelKey.secretAccessKey,\n          };\n          await addOrUpdateDotEnvironmentFile(collectionId, {\n            ...collection.dotEnvVariables,\n            ...vars,\n          });\n        }\n        const generatedNodes = await ipcRenderer.invoke('renderer:generate-nodes-ai', instruction, collectionId, {\n          name: GENAI_MODELS.bedrock_claude,\n          apiKey: modelKey,\n        });\n        const flowData = {\n          nodes: translateGeneratedNodesToOpenApiNodes(generatedNodes, collection.nodes),\n        };\n        return flowData;\n      } else if (modelName === GENAI_MODELS.gemini) {\n        if (!collection.dotEnvVariables) {\n          await ipcRenderer.invoke('renderer:create-dotenv', collection.pathname, `GEMINI_APIKEY=${modelKey}`);\n        } else if (\n          !Object.prototype.hasOwnProperty.call(collection.dotEnvVariables, 'GEMINI_APIKEY') ||\n          modelKey != collection.dotEnvVariables['GEMINI_APIKEY']\n        ) {\n          await addOrUpdateDotEnvironmentFile(collectionId, {\n            ...collection.dotEnvVariables,\n            GEMINI_APIKEY: modelKey,\n          });\n        }\n        const generatedNodes = await ipcRenderer.invoke('renderer:generate-nodes-ai', instruction, collectionId, {\n          name: GENAI_MODELS.gemini,\n          apiKey: modelKey,\n        });\n        const flowData = {\n          nodes: translateGeneratedNodesToOpenApiNodes(generatedNodes, collection.nodes),\n        };\n        return flowData;\n      } else {\n        return Promise.reject(new Error(`model: ${modelName} not supported`));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    return Promise.reject(error);\n  }\n};\n"
  },
  {
    "path": "src/components/molecules/flow/graph/Graph.js",
    "content": "// assumption is that apis are giving json as output\n\nimport { cloneDeep } from 'lodash';\nimport { readFlowTestSync } from 'service/collection';\nimport authNode from './compute/authnode';\nimport nestedFlowNode from './compute/nestedflownode';\nimport assertNode from './compute/assertnode';\nimport requestNode from './compute/requestnode';\nimport setVarNode from './compute/setvarnode';\nimport { LogLevel } from './GraphLogger';\nimport { useTabStore } from 'stores/TabStore';\n\nclass Graph {\n  constructor(nodes, edges, startTime, initialEnvVars, logger, collectionPath, timeout, tab) {\n    this.nodes = nodes;\n    this.edges = edges;\n    this.logger = logger;\n    this.timeout = timeout;\n    this.startTime = startTime;\n    this.graphRunNodeOutput = {};\n    this.auth = undefined;\n    this.envVariables = initialEnvVars;\n    //this.caller = caller;\n    this.collectionPath = collectionPath;\n    this.tab = tab;\n  }\n\n  #checkTimeout() {\n    return Date.now() - this.startTime > this.timeout;\n  }\n\n  #computeConnectingEdge(node, result) {\n    let connectingEdge = undefined;\n\n    if (node.type === 'assertNode') {\n      if (result.output === true) {\n        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'true' && edge.source === node.id);\n      } else {\n        connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'false' && edge.source === node.id);\n      }\n    } else {\n      if (result.status === 'Success') {\n        connectingEdge = this.edges.find((edge) => edge.source === node.id);\n      }\n    }\n\n    return connectingEdge;\n  }\n\n  #computeDataFromPreviousNodes(node) {\n    var prevNodesData = {};\n    // a request node is allowed multiple incoming edges\n    this.edges.forEach((edge) => {\n      if (edge.target === node.id) {\n        if (this.graphRunNodeOutput[edge.source]) {\n          prevNodesData = {\n            ...prevNodesData,\n            ...this.graphRunNodeOutput[edge.source],\n          };\n        }\n      }\n    });\n    return prevNodesData;\n  }\n\n  async #computeNode(node) {\n    let result = undefined;\n    const prevNodeOutputData = this.#computeDataFromPreviousNodes(node);\n\n    try {\n      console.debug('Executing node: ', node);\n\n      if (node.type === 'outputNode') {\n        this.logger.add(LogLevel.INFO, '', { type: 'outputNode', data: { output: prevNodeOutputData } });\n        if (this.tab) {\n          const updatedNode = {\n            ...node,\n            data: {\n              ...node.data,\n              output: prevNodeOutputData,\n            },\n          };\n          useTabStore.getState().updateFlowTestNode(this.tab.id, updatedNode);\n        }\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'assertNode') {\n        const eNode = new assertNode(\n          node.data.operator,\n          node.data.variables,\n          prevNodeOutputData,\n          this.envVariables,\n          this.logger,\n        );\n        if (eNode.evaluate()) {\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n            output: true,\n          };\n        } else {\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n            output: false,\n          };\n        }\n      }\n\n      if (node.type === 'delayNode') {\n        const delay = node.data.delay;\n        const wait = (ms) => {\n          return new Promise((resolve) => setTimeout(resolve, Math.min(ms, this.timeout)));\n        };\n        await wait(delay);\n        this.logger.add(LogLevel.INFO, '', { type: 'delayNode', data: { delay } });\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'authNode') {\n        const aNode = new authNode(node.data, this.envVariables, this.logger);\n        this.auth = node.data.type ? aNode.evaluate() : undefined;\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (node.type === 'requestNode') {\n        const rNode = new requestNode(\n          node.data,\n          prevNodeOutputData,\n          this.envVariables,\n          this.auth,\n          this.logger,\n          this.collectionPath,\n        );\n        result = await rNode.evaluate();\n        // add post response variables if any\n        if (result.postRespVars) {\n          this.envVariables = {\n            ...this.envVariables,\n            ...result.postRespVars,\n          };\n        }\n      }\n\n      if (node.type === 'flowNode') {\n        const flowData = await readFlowTestSync(node.data.relativePath);\n        if (flowData) {\n          const cNode = new nestedFlowNode(\n            cloneDeep(flowData.nodes),\n            cloneDeep(flowData.edges),\n            this.startTime,\n            this.envVariables,\n            this.logger,\n            //node.type,\n            this.collectionPath,\n            this.timeout,\n          );\n          result = await cNode.evaluate();\n          this.envVariables = result.envVars;\n        } else {\n          result = {\n            status: 'Success',\n            data: prevNodeOutputData,\n          };\n        }\n      }\n\n      if (node.type === 'setVarNode') {\n        const sNode = new setVarNode(node.data, prevNodeOutputData, this.envVariables);\n        const newVariable = sNode.evaluate();\n        if (newVariable != undefined) {\n          this.logger.add(LogLevel.INFO, '', {\n            type: 'setVarNode',\n            data: {\n              name: Object.keys(newVariable)[0],\n              value: newVariable[Object.keys(newVariable)[0]],\n            },\n          });\n          this.envVariables = {\n            ...this.envVariables,\n            ...newVariable,\n          };\n        }\n        result = {\n          status: 'Success',\n          data: prevNodeOutputData,\n        };\n      }\n\n      if (this.#checkTimeout()) {\n        throw Error(`Timeout of ${this.timeout} ms exceeded, stopping graph run`);\n      }\n    } catch (err) {\n      this.logger.add(LogLevel.ERROR, `Flow failed due to ${err}`, {\n        type: 'errorNode',\n        data: node.data,\n      });\n      return {\n        status: 'Failed',\n      };\n    }\n\n    if (result === undefined) {\n      this.logger.add(LogLevel.ERROR, 'Flow failed due to failure to evaluate result', {\n        type: 'errorNode',\n        data: node.data,\n      });\n      return {\n        status: 'Failed',\n      };\n    } else {\n      const connectingEdge = this.#computeConnectingEdge(node, result);\n\n      if (connectingEdge != undefined) {\n        const nextNode = this.nodes.find(\n          (node) =>\n            ['requestNode', 'outputNode', 'assertNode', 'delayNode', 'authNode', 'flowNode', 'setVarNode'].includes(\n              node.type,\n            ) && node.id === connectingEdge.target,\n        );\n        this.graphRunNodeOutput[node.id] = result.data ? result.data : {};\n        return this.#computeNode(nextNode);\n      } else {\n        return result;\n      }\n    }\n  }\n\n  async run() {\n    if (this.tab) {\n      const updatedNodes = this.nodes.map((node) => {\n        if (node.type === 'outputNode') {\n          if (node.data.output) {\n            const { ['output']: _, ...data } = node.data;\n            node.data = data;\n          }\n        }\n\n        return node;\n      });\n      useTabStore.getState().updateFlowTestNodes(this.tab.id, updatedNodes);\n    }\n\n    this.graphRunNodeOutput = {};\n\n    this.logger.add(LogLevel.INFO, 'Start Flowtest');\n    const startNode = this.nodes.find((node) => node.type === 'startNode');\n    if (startNode == undefined) {\n      this.logger.add(LogLevel.INFO, 'No start node found');\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: 'Success',\n        envVars: this.envVariables,\n      };\n    }\n    const connectingEdge = this.edges.find((edge) => edge.source === startNode.id);\n\n    // only start computing graph if initial node has the connecting edge\n    if (connectingEdge != undefined) {\n      const firstNode = this.nodes.find((node) => node.id === connectingEdge.target);\n      const result = await this.#computeNode(firstNode);\n      // if (result.status == 'Failed') {\n      //   console.debug('Flow failed at: ', result.node);\n      // }\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: result.status,\n        envVars: this.envVariables,\n      };\n    } else {\n      this.logger.add(LogLevel.INFO, 'End Flowtest');\n      return {\n        status: 'Success',\n        envVars: this.envVariables,\n      };\n    }\n  }\n}\n\nexport default Graph;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/GraphLogger.js",
    "content": "export const LogLevel = Object.freeze({\n  INFO: 0,\n  WARN: 1,\n  ERROR: 2,\n});\n\nclass GraphLogger {\n  constructor() {\n    this.logs = [];\n  }\n\n  add(logLevel, message, node) {\n    this.logs.push({\n      logLevel,\n      timestamp: new Date().toISOString(),\n      message,\n      node,\n    });\n  }\n\n  get() {\n    return this.logs;\n  }\n}\n\nexport default GraphLogger;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/GraphRun.js",
    "content": "import GraphLogger, { LogLevel } from './GraphLogger';\nimport Graph from './Graph';\nimport { useTabStore } from 'stores/TabStore';\nimport { cloneDeep } from 'lodash';\nimport { uploadGraphRunLogs } from 'service/collection';\nimport { toast } from 'react-toastify';\n\nconst postResult = async (tab, status, time, logs) => {\n  const response = await uploadGraphRunLogs(tab.name, status, time, logs);\n  useTabStore.getState().updateFlowTestLogs(tab.id, status, logs, response);\n  useTabStore.getState().updateFlowTestRunStatus(tab.id, false);\n  if (status == 'Success') {\n    toast.success(`FlowTest Run Success!`);\n  } else if (status == 'Failed') {\n    toast.error(`FlowTest Run Failed!`);\n  }\n};\n\nexport const graphRun = async (tab, nodes, edges, timeout, collectionPath, selectedEnv) => {\n  useTabStore.getState().updateFlowTestRunStatus(tab.id, true);\n\n  const startTime = Date.now();\n  const logger = new GraphLogger();\n  try {\n    let envVariables = {};\n\n    if (selectedEnv) {\n      envVariables = cloneDeep(selectedEnv.variables);\n    }\n\n    // ============= flow =====================\n    const g = new Graph(nodes, edges, startTime, envVariables, logger, collectionPath, timeout, tab);\n    const result = await g.run();\n    const time = Date.now() - startTime;\n    logger.add(LogLevel.INFO, `Total time: ${time} ms`);\n\n    await postResult(tab, result.status, time, logger.get());\n  } catch (error) {\n    const time = Date.now() - startTime;\n    logger.add(LogLevel.ERROR, 'Internal error running graph');\n    logger.add(LogLevel.INFO, `Total time: ${time} ms`);\n    await postResult(tab, 'Failed', time, logger.get());\n  }\n};\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/assertnode.js",
    "content": "import AssertOperators from '../../constants/assertOperators';\nimport { computeNodeVariable } from './utils';\nimport Node from './node';\nimport { LogLevel } from '../GraphLogger';\n\nclass assertNode extends Node {\n  constructor(operator, variables, prevNodeOutputData, envVariables, logger) {\n    super('assertNode');\n    this.operator = operator;\n    this.variables = variables;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.logger = logger;\n    this.envVariables = envVariables;\n  }\n\n  getVariableValue(variable) {\n    if (variable.type.toLowerCase() === 'variable') {\n      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {\n        return this.envVariables[variable.value];\n      } else {\n        throw Error(`Cannot find value of variable ${variable.value}`);\n      }\n    } else {\n      return computeNodeVariable(variable, this.prevNodeOutputData);\n    }\n  }\n\n  evaluate() {\n    console.log('Evaluating an assert node');\n    const var1 = this.getVariableValue(this.variables.var1);\n    const var2 = this.getVariableValue(this.variables.var2);\n\n    const operator = this.operator;\n    if (operator == undefined) {\n      throw Error('Operator undefined');\n    }\n\n    let result;\n    switch (operator) {\n      case AssertOperators.isEqualTo:\n        result = var1 === var2;\n        break;\n      case AssertOperators.isNotEqualTo:\n        result = var1 != var2;\n        break;\n      case AssertOperators.isGreaterThan:\n        result = var1 > var2;\n        break;\n      case AssertOperators.isLessThan:\n        result = var1 < var2;\n        break;\n      default:\n        throw Error('Unsupported operator');\n    }\n    this.logger.add(LogLevel.INFO, '', { type: 'assertNode', data: { var1, var2, operator, result } });\n\n    return result;\n  }\n}\n\nexport default assertNode;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/authnode.js",
    "content": "import { computeVariables } from './utils';\nimport Node from './node';\nimport { LogLevel } from '../GraphLogger';\n\nclass authNode extends Node {\n  constructor(nodeData, envVariables, logger) {\n    super('authNode');\n    (this.nodeData = nodeData), (this.envVariables = envVariables);\n    this.logger = logger;\n  }\n\n  evaluate() {\n    console.log('Evaluating an auth node');\n    if (this.nodeData.type === 'basic-auth') {\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Basic Authentication' } });\n      const username = computeVariables(this.nodeData.username, this.envVariables);\n      const password = computeVariables(this.nodeData.password, this.envVariables);\n\n      return {\n        type: 'basic-auth',\n        username,\n        password,\n      };\n    } else if (this.nodeData.type === 'bearer-token') {\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'Bearer Token' } });\n      const token = computeVariables(this.nodeData.token, this.envVariables);\n      return {\n        type: 'bearer-token',\n        token,\n      };\n    } else if (this.nodeData.type === 'no-auth') {\n      this.logger.add(LogLevel.INFO, '', { type: 'authNode', data: { authType: 'No Authentication' } });\n      return {\n        type: 'no-auth',\n      };\n    } else {\n      throw Error(`auth type: ${this.nodeData.type} is not valid`);\n    }\n  }\n}\n\nexport default authNode;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/nestedflownode.js",
    "content": "import Graph1 from '../Graph';\nimport Node from './node';\n\nclass nestedFlowNode extends Node {\n  constructor(nodes, edges, startTime, initialEnvVars, logger, collectionPath, timeout) {\n    super('flowNode');\n    this.internalGraph = new Graph1(nodes, edges, startTime, initialEnvVars, logger, collectionPath, timeout);\n  }\n\n  async evaluate() {\n    console.log('Evaluating a complex node (nested graph):');\n    return this.internalGraph.run();\n  }\n}\n\nexport default nestedFlowNode;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/node.js",
    "content": "class Node {\n  constructor(type) {\n    this.type = type;\n  }\n\n  evaluate() {\n    throw new Error('Evaluate method must be implemented by subclasses');\n  }\n}\n\nexport default Node;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/requestnode.js",
    "content": "import { cloneDeep } from 'lodash';\nimport { computeNodeVariables, computeVariables } from '../compute/utils';\nimport { LogLevel } from '../GraphLogger';\nimport Node from './node';\n\nclass requestNode extends Node {\n  constructor(nodeData, prevNodeOutputData, envVariables, auth, logger, collectionPath) {\n    super('requestNode');\n    this.nodeData = nodeData;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.envVariables = envVariables;\n    this.auth = auth;\n    this.logger = logger;\n    this.collectionPath = collectionPath;\n  }\n\n  async evaluate() {\n    console.log('Evaluating request node');\n    // step1 evaluate pre request variables of this node\n    const evalVariables = computeNodeVariables(this.nodeData.preReqVars, this.prevNodeOutputData);\n\n    const variablesDict = {\n      ...this.envVariables,\n      ...evalVariables,\n    };\n    console.debug('Avialable variables: ', variablesDict);\n\n    // step2 replace variables in url with value\n    const finalUrl = computeVariables(this.nodeData.url, variablesDict);\n    console.debug('Evaluated Url: ', finalUrl);\n\n    // step 3\n    const rawRequest = await this.formulateRequest(finalUrl, variablesDict);\n\n    const { request, response } = await this.runHttpRequest(rawRequest);\n\n    if (response.error) {\n      this.logger.add(LogLevel.ERROR, 'HTTP request failed', {\n        type: 'requestNode',\n        data: {\n          request,\n          response: response.error,\n          preReqVars: evalVariables,\n        },\n      });\n      return {\n        status: 'Failed',\n      };\n    } else {\n      if (this.nodeData.postRespVars) {\n        const evalPostRespVars = computeNodeVariables(this.nodeData.postRespVars, response.data);\n        this.logger.add(LogLevel.INFO, 'HTTP request success', {\n          type: 'requestNode',\n          data: {\n            request,\n            response,\n            preReqVars: evalVariables,\n            postRespVars: evalPostRespVars,\n          },\n        });\n        return {\n          status: 'Success',\n          data: response.data,\n          postRespVars: evalPostRespVars,\n        };\n      }\n      this.logger.add(LogLevel.INFO, 'HTTP request success', {\n        type: 'requestNode',\n        data: {\n          request,\n          response,\n          preReqVars: evalVariables,\n        },\n      });\n      return {\n        status: 'Success',\n        data: response.data,\n      };\n    }\n  }\n\n  async formulateRequest(finalUrl, variablesDict) {\n    let restMethod = this.nodeData.requestType.toLowerCase();\n    let headers = {};\n    let requestData = undefined;\n\n    if (this.nodeData.requestBody) {\n      if (this.nodeData.requestBody.type === 'raw-json') {\n        headers['content-type'] = 'application/json';\n        requestData = this.nodeData.requestBody.body\n          ? JSON.parse(computeVariables(this.nodeData.requestBody.body, variablesDict))\n          : JSON.parse('{}');\n      } else if (this.nodeData.requestBody.type === 'form-data') {\n        headers['content-type'] = 'multipart/form-data';\n        const params = cloneDeep(this.nodeData.requestBody.body);\n        requestData = params;\n      }\n    }\n\n    if (this.nodeData.headers && this.nodeData.headers.length > 0) {\n      this.nodeData.headers.map((pair, index) => {\n        headers[computeVariables(pair.name, variablesDict)] = computeVariables(pair.value, variablesDict);\n      });\n    }\n\n    if (this.auth && this.auth?.type === 'bearer-token') {\n      headers['Authorization'] = `Bearer ${this.auth.token}`;\n    }\n\n    const options = {\n      method: restMethod,\n      url: finalUrl,\n      headers,\n      data: requestData,\n    };\n\n    if (this.auth && this.auth?.type === 'basic-auth') {\n      options.auth = {};\n      options.auth.username = this.auth.username;\n      options.auth.password = this.auth.password;\n    }\n\n    return options;\n  }\n\n  runHttpRequest(rawRequest) {\n    const { ipcRenderer } = window;\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer.invoke('renderer:run-http-request', rawRequest, this.collectionPath).then(resolve).catch(reject);\n    });\n  }\n}\n\nexport default requestNode;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/setvarnode.js",
    "content": "import { computeNodeVariable } from './utils';\nimport Node from './node';\nimport EvaluateOperators from '../../constants/evaluateOperators';\n\nclass setVarNode extends Node {\n  constructor(nodeData, prevNodeOutputData, envVariables) {\n    super('setVarNode');\n    this.nodeData = nodeData;\n    this.prevNodeOutputData = prevNodeOutputData;\n    this.envVariables = envVariables;\n  }\n\n  getVariableValue(variable) {\n    if (variable.type.toLowerCase() === 'variable') {\n      if (Object.prototype.hasOwnProperty.call(this.envVariables, variable.value)) {\n        return this.envVariables[variable.value];\n      } else {\n        throw Error(`Cannot find value of variable ${variable.value}`);\n      }\n    } else {\n      return computeNodeVariable(variable, this.prevNodeOutputData);\n    }\n  }\n\n  evaluate() {\n    console.log('Evaluating set variable node');\n    if (this.nodeData.variable) {\n      if (this.nodeData.variable.name && this.nodeData.variable.name.trim() != '') {\n        const vName = this.nodeData.variable.name;\n        const vType = this.nodeData.variable.type.trim();\n        if (['String', 'Number', 'Boolean', 'Now', 'Select'].includes(vType)) {\n          const value = computeNodeVariable(this.nodeData.variable, this.prevNodeOutputData);\n          return {\n            [vName]: value,\n          };\n        } else if (vType === 'Expression') {\n          const variables = this.nodeData.variable.value.variables;\n          if (variables && variables.var1 && variables.var2) {\n            const var1 = this.getVariableValue(this.nodeData.variable.value.variables.var1);\n            const var2 = this.getVariableValue(this.nodeData.variable.value.variables.var2);\n\n            const operator = this.nodeData.variable.value.operator;\n            if (operator == undefined) {\n              throw 'Operator undefined';\n            }\n            if (operator == EvaluateOperators.Add) {\n              if (typeof var1 !== 'number' || typeof var2 !== 'number') {\n                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);\n              }\n              return {\n                [vName]: var1 + var2,\n              };\n            } else if (operator == EvaluateOperators.Subtract) {\n              if (typeof var1 !== 'number' || typeof var2 !== 'number') {\n                throw Error(`Cannot perform ${typeof var1} + ${typeof var2}`);\n              }\n              return {\n                [vName]: var1 - var2,\n              };\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nexport default setVarNode;\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/utils.js",
    "content": "export const computeNodeVariable = (variable, prevNodeOutputData) => {\n  if (variable.type.toLowerCase() === 'string') {\n    return variable.value.toString();\n  }\n\n  if (variable.type.toLowerCase() === 'number') {\n    return Number(variable.value);\n  }\n\n  if (variable.type.toLowerCase() === 'boolean') {\n    return Boolean(variable.value);\n  }\n\n  if (variable.type.toLowerCase() === 'now') {\n    return Date.now();\n  }\n\n  if (variable.type.toLowerCase() === 'select') {\n    try {\n      if (prevNodeOutputData == undefined || Object.keys(prevNodeOutputData).length === 0) {\n        console.debug(\n          `Cannot evaluate variable ${variable} as previous node output data ${JSON.stringify(prevNodeOutputData)} is empty`,\n        );\n        throw Error(`Cannot evaluate variable ${variable.value}`);\n      }\n      const jsonTree = variable.value.split('.');\n      const getVal = (parent, pos) => {\n        if (pos == jsonTree.length) {\n          return parent;\n        }\n        const key = jsonTree[pos];\n        if (key == '') {\n          return parent;\n        }\n\n        return getVal(parent[key], pos + 1);\n      };\n      const result = getVal(prevNodeOutputData, 0);\n      if (result == undefined) {\n        console.debug(\n          `Cannot evaluate variable ${JSON.stringify(variable)} as previous node output data ${JSON.stringify(prevNodeOutputData)} did not contain the variable`,\n        );\n        throw Error(`Cannot evaluate variable ${variable.value}`);\n      }\n      return result;\n    } catch (error) {\n      throw Error(`Cannot evaluate variable ${variable.value}`);\n    }\n  }\n};\n\nexport const computeNodeVariables = (variables, prevNodeOutputData) => {\n  const evalVariables = {};\n  if (variables) {\n    Object.entries(variables).map(([vname, variable]) => {\n      evalVariables[vname] = computeNodeVariable(variable, prevNodeOutputData);\n    });\n  }\n  return evalVariables;\n};\n\nexport const computeVariables = (str, variablesDict) => {\n  const regex = /\\{\\{(.+)\\}\\}/;\n  const foundRegex = regex.exec(str);\n  if (foundRegex) {\n    const match = str.match(/{{([^}]+)}}/);\n    if (variablesDict) {\n      if (Object.prototype.hasOwnProperty.call(variablesDict, match[1])) {\n        const varValue = variablesDict[`${match[1]}`];\n        return computeVariables(str.replaceAll(match[0], varValue), variablesDict);\n      } else {\n        throw Error(`Cannot find value of variable ${match[1]}`);\n      }\n    } else {\n      throw Error(`Cannot compute variable ${match[1]} as dict is empty`);\n    }\n  } else {\n    return str;\n  }\n};\n"
  },
  {
    "path": "src/components/molecules/flow/graph/compute/utils.test.js",
    "content": "const { computeVariables } = require('./utils');\n\ndescribe('Utils', () => {\n  it('should compute variables correctly', () => {\n    let str = 'hello {{var1}}! hello {{var1}}! bye';\n    let dict = {\n      var1: 'world',\n    };\n\n    let result = computeVariables(str, dict);\n    expect(result).toEqual('hello world! hello world! bye');\n\n    str = 'hello {{var1}}! hello {{var2}}! bye';\n    expect(() => {\n      computeVariables(str, dict);\n    }).toThrow(Error);\n\n    str = 'hello {{var1}}! hello {{var2}}! bye';\n    dict = null;\n    expect(() => {\n      computeVariables(str, dict);\n    }).toThrow(Error);\n\n    dict = {\n      var1: 'world',\n      var2: 'person',\n    };\n    result = computeVariables(str, dict);\n    expect(result).toEqual('hello world! hello person! bye');\n\n    str = 'hello world!';\n    result = computeVariables(str, dict);\n    expect(result).toEqual(str);\n  });\n});\n"
  },
  {
    "path": "src/components/molecules/flow/index.js",
    "content": "import React, { useCallback, useMemo, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport ReactFlow, { Controls, Background, ControlButton, useKeyPress } from 'reactflow';\nimport 'reactflow/dist/style.css';\nimport { cloneDeep } from 'lodash';\nimport { toast } from 'react-toastify';\n\n// ReactFlow Canvas\nimport CustomEdge from './edges/ButtonEdge';\n\nimport AddNodes from './AddNodes';\nimport RequestNode from './nodes/RequestNode';\nimport OutputNode from './nodes/OutputNode';\nimport AssertNode from './nodes/AssertNode';\nimport DelayNode from './nodes/DelayNode';\nimport AuthNode from './nodes/AuthNode';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport useCanvasStore from 'stores/CanvasStore';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { useTabStore } from 'stores/TabStore';\nimport Graph from './graph/Graph';\nimport NestedFlowNode from './nodes/NestedFlowNode';\nimport { initFlowData } from './utils';\nimport SetVarNode from './nodes/SetVarNode';\nimport { saveHandle } from '../modals/SaveFlowModal';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport GraphLogger, { LogLevel } from './graph/GraphLogger';\nimport Mousetrap from 'mousetrap';\nimport { uploadGraphRunLogs } from 'service/collection';\nimport { graphRun } from './graph/GraphRun';\n\nconst StartNode = () => (\n  <FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>\n);\n\nexport const init = (flowData) => {\n  // Initialization\n  if (flowData && flowData.nodes && flowData.edges) {\n    return {\n      nodes: cloneDeep(flowData.nodes),\n      edges: cloneDeep(flowData.edges),\n      viewport: cloneDeep(flowData.viewport),\n    };\n  } else if (flowData && flowData.nodes && !flowData.edges) {\n    // AI prompt generated\n    const nodes = cloneDeep(initFlowData.nodes);\n    const edges = cloneDeep(initFlowData.edges);\n    for (let i = 0; i < flowData.nodes.length; i++) {\n      nodes.push({\n        id: `${i + 2}`,\n        type: flowData.nodes[i].type,\n        position: { x: 150 + (i + 1) * 500, y: 50 },\n        data: flowData.nodes[i],\n      });\n      edges.push({\n        id: `reactflow__edge-${i + 1}-${i + 2}`,\n        source: `${i + 1}`,\n        sourceHandle: null,\n        target: `${i + 2}`,\n        targetHandle: null,\n        type: 'buttonedge',\n      });\n    }\n    return {\n      nodes,\n      edges,\n      viewport: cloneDeep(initFlowData.viewport),\n    };\n  } else {\n    return cloneDeep(initFlowData);\n  }\n};\n\nconst selector = (state) => ({\n  nodes: state.nodes,\n  edges: state.edges,\n  onNodesChange: state.onNodesChange,\n  onEdgesChange: state.onEdgesChange,\n  onConnect: state.onConnect,\n  setNodes: state.setNodes,\n  setEdges: state.setEdges,\n  viewport: state.viewport,\n  setViewport: state.setViewport,\n});\n\nconst Flow = ({ tab, collectionId }) => {\n  const { nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, viewport, setViewport } =\n    useCanvasStore(selector);\n\n  const setLogs = useTabStore((state) => state.updateFlowTestLogs);\n\n  const [reactFlowInstance, setReactFlowInstance] = useState(null);\n\n  const nodeTypes = useMemo(\n    () => ({\n      startNode: StartNode,\n      requestNode: RequestNode,\n      outputNode: OutputNode,\n      assertNode: AssertNode,\n      delayNode: DelayNode,\n      authNode: AuthNode,\n      flowNode: NestedFlowNode,\n      setVarNode: SetVarNode,\n    }),\n    [],\n  );\n\n  const edgeTypes = useMemo(\n    () => ({\n      buttonedge: CustomEdge,\n    }),\n    [],\n  );\n\n  const onDragOver = useCallback((event) => {\n    event.preventDefault();\n    event.dataTransfer.dropEffect = 'move';\n  }, []);\n\n  const getId = () => {\n    const currentIds = reactFlowInstance.getNodes().map((node) => Number(node.id));\n    const currentMaxId = Math.max(...currentIds);\n    return `${currentMaxId + 1}`;\n  };\n\n  const onDrop = useCallback(\n    (event) => {\n      event.preventDefault();\n\n      let nodeData = event.dataTransfer.getData('application/reactflow');\n\n      // check if the dropped element is valid\n      if (typeof nodeData === 'undefined' || !nodeData) {\n        return;\n      }\n\n      nodeData = JSON.parse(nodeData);\n\n      const position = reactFlowInstance.screenToFlowPosition({\n        x: event.clientX,\n        y: event.clientY,\n      });\n      const newNode = {\n        id: getId(),\n        type: nodeData.type,\n        position,\n        data: nodeData,\n      };\n      console.debug('Dropped node: ', newNode);\n\n      setNodes([...useCanvasStore.getState().nodes, newNode]);\n    },\n    [reactFlowInstance],\n  );\n\n  const isValidConnection = (connection) => {\n    // Only 1 outgoing edge from each (source, sourceHandle) is allowed\n    // as we only support sequential graphs for now\n    reactFlowInstance.getEdges().map((edge) => {\n      if (connection.source === edge.source && connection.sourceHandle === edge.sourceHandle) {\n        return false;\n      }\n    });\n\n    const nodes = reactFlowInstance.getNodes();\n\n    // self connecting edge not allowed\n    const sourceNode = nodes.find((node) => node.id === connection.source).id;\n    const targetNode = nodes.find((node) => node.id === connection.target).id;\n    if (sourceNode === targetNode) {\n      return false;\n    }\n\n    // multiple incoming edges are only allowed for request nodes\n    const nodeType = nodes.find((node) => node.id === connection.target).type;\n    if (nodeType != 'requestNode') {\n      reactFlowInstance.getEdges().map((edge) => {\n        if (connection.target === edge.target) {\n          return false;\n        }\n      });\n    }\n\n    return true;\n  };\n\n  reactFlowInstance?.setViewport(viewport);\n\n  //const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s', 'Ctrl+s']);\n  // Bind Ctrl+S and Cmd+S\n  Mousetrap.bind(['ctrl+s', 'command+s'], function (e) {\n    e.preventDefault();\n    saveHandle(tab);\n    return false;\n  });\n\n  return (\n    <div className='flex-auto'>\n      <ReactFlow\n        key={tab.id}\n        nodes={nodes}\n        edges={edges.map((edge) => ({\n          ...edge,\n          animated: tab.running,\n        }))}\n        onNodesChange={onNodesChange}\n        onEdgesChange={onEdgesChange}\n        onConnect={onConnect}\n        nodeTypes={nodeTypes}\n        edgeTypes={edgeTypes}\n        onInit={setReactFlowInstance}\n        onDrop={onDrop}\n        onDragOver={onDragOver}\n        defaultViewport={{\n          x: viewport.x,\n          y: viewport.y,\n          zoom: viewport.zoom,\n        }}\n        onMoveEnd={(event, data) => {\n          setViewport(data);\n        }}\n        minZoom={0}\n        maxZoom={2}\n        isValidConnection={isValidConnection}\n      >\n        <Background variant='dots' gap={12} size={1} />\n        <Controls\n          className='flex border-cyan-900 shadow-none'\n          onFitView={() => setViewport(reactFlowInstance.getViewport())}\n        ></Controls>\n        <Button\n          classes={'absolute bottom-4 right-20 z-[2000] text-xl'}\n          btnType={BUTTON_TYPES.primary}\n          isDisabled={false}\n          onClickHandle={() => {\n            const activeCollection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n            const activeEnv = activeCollection?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);\n            const nodes = cloneDeep(reactFlowInstance.getNodes());\n            const edges = cloneDeep(reactFlowInstance.getEdges());\n            const timeout = useCanvasStore.getState().timeout;\n\n            graphRun(tab, nodes, edges, timeout, activeCollection?.pathname, activeEnv);\n          }}\n          fullWidth={false}\n        >\n          Run\n        </Button>\n        <AddNodes collectionId={collectionId} />\n      </ReactFlow>\n    </div>\n  );\n};\n\nFlow.propTypes = {\n  collectionId: PropTypes.string.isRequired,\n};\n\nexport default Flow;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/AssertNode.js",
    "content": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Handle, Position } from 'reactflow';\nimport AssertOperators from '../constants/assertOperators';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport { getInputType } from 'utils/common';\nimport { CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ } from 'constants/Common';\nimport useCanvasStore from 'stores/CanvasStore';\n\n// ToDo: Change standard select element(s) with headless list element\nconst operatorMenu = (id, data) => {\n  const setAssertNodeOperator = useCanvasStore((state) => state.setAssertNodeOperator);\n\n  const handleOperatorSelection = (event) => {\n    const selectedValue = event.target?.value;\n    // ToDO: verify the behavior when use selects the default item\n    if (selectedValue !== CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value) {\n      setAssertNodeOperator(id, selectedValue);\n    }\n  };\n\n  // ToDo: Change standard select element(s) with headless list element\n  return (\n    <div className='mb-4'>\n      <select\n        onChange={handleOperatorSelection}\n        name='operator-type'\n        value={data.operator ? data.operator : CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value}\n        className='w-full h-12 p-2 text-left border rounded outline-none cursor-default bg-background-light border-cyan-950'\n      >\n        <option value={CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value}>\n          {CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.displayValue}\n        </option>\n        <option value={AssertOperators.isLessThan}>{AssertOperators.isLessThan}</option>\n        <option value={AssertOperators.isGreaterThan}>{AssertOperators.isGreaterThan}</option>\n        <option value={AssertOperators.isEqualTo}>{AssertOperators.isEqualTo}</option>\n        <option value={AssertOperators.isNotEqualTo}>{AssertOperators.isNotEqualTo}</option>\n      </select>\n    </div>\n  );\n};\n\n// ToDo: refactor component parameters, make it more readable. vname and data are not suited\nconst variableElem = (id, data, varName) => {\n  const setAssertNodeVariable = useCanvasStore((state) => state.setAssertNodeVariable);\n\n  const handleInputTypeSelection = (event) => {\n    const selectedValue = event.target?.value;\n    switch (selectedValue) {\n      case 'String':\n        setAssertNodeVariable(id, varName, selectedValue, '');\n        break;\n      case 'Select':\n        setAssertNodeVariable(id, varName, selectedValue, '');\n        break;\n      case 'Variable':\n        setAssertNodeVariable(id, varName, selectedValue, '');\n        break;\n      case 'Number':\n        setAssertNodeVariable(id, varName, selectedValue, 0);\n        break;\n      case 'Boolean':\n        setAssertNodeVariable(id, varName, selectedValue, false);\n        break;\n      case 'Now':\n        setAssertNodeVariable(id, varName, selectedValue, '');\n        break;\n    }\n  };\n\n  return (\n    <div className='flex items-center justify-center mb-4 text-sm border rounded outline-none cursor-default bg-background-light border-cyan-950'>\n      {data.variables && data.variables[varName] ? (\n        data.variables[varName].type === 'Boolean' ? (\n          <select\n            onChange={(event) => setAssertNodeVariable(id, varName, 'Boolean', event.target?.value)}\n            name='boolean-val'\n            className='nodrag h-12 w-full bg-transparent p-2.5 px-1 outline-none'\n            value={data.variables[varName].value}\n          >\n            <option value='true'>True</option>\n            <option value='false'>False</option>\n          </select>\n        ) : data.variables[varName].type === 'Now' ? (\n          <div></div>\n        ) : (\n          <input\n            id='outlined-adornment-weight'\n            type={getInputType(data.variables[varName].type)}\n            className='nodrag nowheel block h-12 w-full bg-transparent p-2.5 outline-none'\n            name='variable-value'\n            placeholder={varName}\n            value={data.variables[varName].value}\n            onChange={(event) => {\n              const updatedValue = event.target.value;\n              switch (data.variables[varName].type) {\n                case 'String':\n                  setAssertNodeVariable(id, varName, 'String', updatedValue.toString());\n                  break;\n                case 'Select':\n                  setAssertNodeVariable(id, varName, 'Select', updatedValue.toString());\n                  break;\n                case 'Variable':\n                  setAssertNodeVariable(id, varName, 'Variable', updatedValue.toString());\n                  break;\n                case 'Number':\n                  setAssertNodeVariable(id, varName, 'Number', parseInt(updatedValue));\n                  break;\n              }\n            }}\n          />\n        )\n      ) : (\n        <input\n          id='outlined-adornment-weight'\n          type='text'\n          className='nodrag nowheel block h-12 w-full bg-transparent p-2.5 outline-none'\n          name='variable-value'\n          placeholder={varName}\n          value=''\n          onChange={(event) => {\n            // default type is string, as soon as we select another type, it goes to above flow\n            const updatedValue = event.target.value;\n            setAssertNodeVariable(id, varName, 'String', updatedValue.toString());\n          }}\n        />\n      )}\n\n      <select\n        onChange={handleInputTypeSelection}\n        name='var-input-type'\n        className='w-full h-8 p-0 px-1 bg-transparent border-l outline-none nodrag border-cyan-950'\n        value={data.variables && data.variables[varName] ? data.variables[varName].type : 'String'}\n      >\n        <option value='Select'>Select</option>\n        <option value='String'>String</option>\n        <option value='Variable'>Variable</option>\n        <option value='Number'>Number</option>\n        <option value='Boolean'>Boolean</option>\n        <option value='Now'>Now</option>\n      </select>\n    </div>\n  );\n};\n\nconst AssertNode = ({ id, data }) => {\n  return (\n    <FlowNode\n      title='Assert'\n      handleLeft={true}\n      handleLeftData={{ type: 'target' }}\n      handleRight={true}\n      handleRightData={{ type: 'source', id: 'true', styles: { bottom: 40, top: 'auto' } }}\n    >\n      <div className='pb-2'>\n        <div>\n          <div>{variableElem(id, data, 'var1')}</div>\n          <div>{operatorMenu(id, data)}</div>\n          <div>{variableElem(id, data, 'var2')}</div>\n        </div>\n        <div className='text-right'>\n          <div className='pb-4'>True</div>\n          <div>False</div>\n        </div>\n      </div>\n      <Handle type='source' position={Position.Right} id='false' style={{ bottom: 0, top: 'auto' }} />\n    </FlowNode>\n  );\n};\n\nAssertNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default AssertNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/AuthNode.js",
    "content": "import React, { useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport TextInput from 'components/atoms/common/TextInput';\nimport { TextEditor } from 'components/atoms/common/TextEditor';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { useTabStore } from 'stores/TabStore';\nimport { cloneDeep } from 'lodash';\n\nconst AuthNode = ({ id, data }) => {\n  const validAuthValues = ['no-auth', 'basic-auth', 'bearer-token'];\n  const setAuthNodeType = useCanvasStore((state) => state.setAuthNodeType);\n  const setBasicAuthValues = useCanvasStore((state) => state.setBasicAuthValues);\n  const setBearerTokenValue = useCanvasStore((state) => state.setBearerTokenValue);\n  const [selected, setSelected] = useState(data.type && validAuthValues.includes(data.type) ? data.type : 'no-auth');\n\n  const handleBasicAuthValueChange = (value, option) => {\n    setBasicAuthValues(id, option, value);\n  };\n\n  const handleBearerTokenChange = (value) => {\n    setBearerTokenValue(id, value);\n  };\n\n  const getActiveVariables = () => {\n    const collectionId = useCanvasStore.getState().collectionId;\n    if (collectionId) {\n      const activeEnv = useCollectionStore\n        .getState()\n        .collections.find((c) => c.id === collectionId)\n        ?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);\n      if (activeEnv) {\n        return Object.keys(cloneDeep(activeEnv.variables));\n      }\n    }\n    return [];\n  };\n\n  const getAuthType = () => {\n    if (selected === 'no-auth') {\n      return 'No Auth';\n    } else if (selected === 'basic-auth') {\n      return 'Basic Auth';\n    } else if (selected === 'bearer-token') {\n      return 'Bearer Token';\n    }\n  };\n\n  return (\n    <>\n      <FlowNode\n        title='Authentication'\n        handleLeft={true}\n        handleLeftData={{ type: 'target' }}\n        handleRight={true}\n        handleRightData={{ type: 'source' }}\n      >\n        <div className='w-52'>\n          <Listbox\n            value={selected}\n            onChange={(selectedValue) => {\n              setSelected(selectedValue);\n              setAuthNodeType(id, selectedValue);\n            }}\n          >\n            <div className='relative'>\n              <Listbox.Button className='relative w-full cursor-default rounded border border-cyan-950 p-2 text-left'>\n                <span className='block truncate'>{getAuthType()}</span>\n                <span className='pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2'>\n                  <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                </span>\n              </Listbox.Button>\n              <Transition\n                as={Fragment}\n                leave='transition ease-in duration-100'\n                leaveFrom='opacity-100'\n                leaveTo='opacity-0'\n              >\n                <Listbox.Options className='absolute z-10 mt-1 max-h-60 w-full overflow-auto bg-white py-1 text-base focus:outline-none'>\n                  <Listbox.Option\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={'no-auth'}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block`}>No Auth</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                  <Listbox.Option\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={'basic-auth'}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block`}>Basic auth</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                  <Listbox.Option\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={'bearer-token'}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block`}>Bearer Token</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                </Listbox.Options>\n              </Transition>\n            </div>\n          </Listbox>\n          {data.type === 'basic-auth' && (\n            <div className='flex flex-col gap-2 py-4'>\n              <TextEditor\n                placeHolder={`Username`}\n                onChangeHandler={(value) => handleBasicAuthValueChange(value, 'username')}\n                name={'username'}\n                value={data.username ? data.username : ''}\n                completionOptions={getActiveVariables()}\n                styles={\n                  'w-full nodrag nowheel block rounded border border-slate-700 bg-background-light p-2.5 text-base outline-none'\n                }\n              />\n              <TextEditor\n                placeHolder={`Password`}\n                onChangeHandler={(value) => handleBasicAuthValueChange(value, 'password')}\n                name={'password'}\n                value={data.password ? data.password : ''}\n                completionOptions={getActiveVariables()}\n                styles={\n                  'w-full nodrag nowheel block rounded border border-slate-700 bg-background-light p-2.5 text-base outline-none'\n                }\n              />\n            </div>\n          )}\n          {data.type === 'bearer-token' && (\n            <div className='flex flex-col gap-2 py-4'>\n              <TextEditor\n                placeHolder={'Token'}\n                onChangeHandler={(value) => handleBearerTokenChange(value)}\n                name={'token'}\n                value={data.token ? data.token : ''}\n                completionOptions={getActiveVariables()}\n                styles={\n                  'w-full nodrag nowheel block rounded border border-slate-700 bg-background-light p-2.5 text-base outline-none'\n                }\n              />\n            </div>\n          )}\n        </div>\n      </FlowNode>\n    </>\n  );\n};\n\nAuthNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default AuthNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/DelayNode.js",
    "content": "import * as React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport useCanvasStore from 'stores/CanvasStore';\nimport NumberInput from 'components/atoms/common/NumberInput';\n\nconst DelayNode = ({ id, data }) => {\n  const setDelayNodeValue = useCanvasStore((state) => state.setDelayNodeValue);\n\n  /**\n   * ToDo: Implement Debouncing for this function\n   */\n  const handleDelayInMsInputChange = (event) => {\n    event.preventDefault();\n    setDelayNodeValue(id, event.target.value);\n  };\n\n  return (\n    <FlowNode\n      title='Delay (ms)'\n      handleLeft={true}\n      handleLeftData={{ type: 'target' }}\n      handleRight={true}\n      handleRightData={{ type: 'source' }}\n    >\n      <NumberInput\n        onChangeHandler={handleDelayInMsInputChange}\n        name={'delay-in-ms'}\n        value={data.delay ? data.delay : 0}\n      />\n    </FlowNode>\n  );\n};\n\nDelayNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default DelayNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/FormDataSelector.js",
    "content": "import React, { useState, Fragment } from 'react';\nimport { ClockIcon } from '@heroicons/react/24/outline';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon, PlusIcon } from '@heroicons/react/20/solid';\n\nexport const optionsData = [\n  { value: 'text', label: 'Text' },\n  { value: 'file', label: 'File' },\n];\n\nconst FormDataSelector = ({ onSelectHandler = () => null }) => {\n  const [selected, setSelected] = useState(null);\n\n  const getSelectedLabel = (selectedValue) => {\n    let labelToShow = '';\n    optionsData.forEach((element) => {\n      if (element.value === selectedValue) {\n        labelToShow = element.label;\n      }\n    });\n    return labelToShow;\n  };\n\n  return (\n    <>\n      <Listbox\n        name='param-selector'\n        value={selected}\n        onChange={(selectedValue) => {\n          setSelected(getSelectedLabel(selectedValue));\n          onSelectHandler(selectedValue);\n        }}\n      >\n        <div className='relative flex h-full'>\n          <Listbox.Button\n            className={`flex items-center justify-between gap-1 sm:text-sm ${optionsData.length ? 'cursor-default' : 'cursor-not-allowed'}`}\n          >\n            <PlusIcon className='w-4 h-4' />\n          </Listbox.Button>\n          {optionsData.length ? (\n            <Transition\n              as={Fragment}\n              leave='transition ease-in duration-100'\n              leaveFrom='opacity-100'\n              leaveTo='opacity-0'\n            >\n              <Listbox.Options className='absolute right-0 z-10 w-full overflow-auto text-base bg-white rounded shadow-lg top-11 max-h-60 min-w-24 ring-1 ring-black/5 sm:text-sm'>\n                {optionsData.map((optionData, optionDataIndex) => (\n                  <Listbox.Option\n                    key={optionDataIndex}\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={optionData.value}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block truncate`}>{optionData.label}</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                            <CheckIcon className='w-5 h-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                ))}\n              </Listbox.Options>\n            </Transition>\n          ) : (\n            ''\n          )}\n        </div>\n      </Listbox>\n    </>\n  );\n};\n\nexport default FormDataSelector;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/NestedFlowNode.js",
    "content": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { getAllFlowTests } from 'stores/utils';\nimport useCollectionStore from 'stores/CollectionStore';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport { readFlowTest } from 'service/collection';\nimport { toast } from 'react-toastify';\n\n// ToDo: Change standard select element(s) with headless list element\nconst NestedFlowNode = ({ id, data }) => {\n  const { ipcRenderer } = window;\n\n  const setFlowForComplexNode = useCanvasStore((state) => state.setFlowForComplexNode);\n  const collectionId = useCanvasStore.getState().collectionId;\n  const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n  const flowTests = getAllFlowTests(collection).map((fullPath) => {\n    return ipcRenderer.relative(collection.pathname, fullPath);\n  });\n\n  if (data.relativePath) {\n    if (!flowTests.find((f) => f === data.relativePath)) {\n      setFlowForComplexNode(id, '');\n    }\n  }\n\n  const handleMouseClick = (event, relativePath) => {\n    if ((event.metaKey || event.ctrlKey) && event.type === 'click') {\n      if (relativePath && relativePath.trim() != '') {\n        readFlowTest(ipcRenderer.join(collection.pathname, relativePath), collectionId)\n          .then((result) => {\n            console.log(`Read flowtest: path = ${relativePath}, collectionId = ${collectionId}`);\n          })\n          .catch((error) => {\n            console.log(`Error reading flowtest: ${error}`);\n            toast.error(`Error reading flowtest`);\n          });\n      }\n    }\n  };\n\n  return (\n    <div onClick={(e) => handleMouseClick(e, data.relativePath)}>\n      <FlowNode\n        title='Flow Node'\n        handleLeft={true}\n        handleLeftData={{ type: 'target' }}\n        handleRight={true}\n        handleRightData={{ type: 'source' }}\n      >\n        <div>\n          <Tippy\n            content={data.relativePath && data.relativePath !== '' ? data.relativePath : 'Select a flow'}\n            placement='top'\n            maxWidth='none'\n          >\n            <select\n              onChange={(event) => {\n                const value = event.target?.value;\n                setFlowForComplexNode(id, value);\n              }}\n              name='flow'\n              value={data.relativePath ? data.relativePath : ''}\n              className='h-12 p-2 border rounded outline-none cursor-default max-w-48 border-cyan-950 bg-background-light'\n            >\n              <option key='None' value=''>\n                Select a flow\n              </option>\n              {flowTests.map((flowTestPath) => {\n                return (\n                  <option key={flowTestPath} value={flowTestPath} className='overflow-scroll'>\n                    {flowTestPath}\n                  </option>\n                );\n              })}\n            </select>\n          </Tippy>\n\n          <p className='hidden'></p>\n        </div>\n      </FlowNode>\n    </div>\n  );\n};\n\nNestedFlowNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default NestedFlowNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/OutputNode.js",
    "content": "import * as React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport { Editor } from 'components/atoms/Editor';\nimport { ArrowsPointingOutIcon } from '@heroicons/react/24/outline';\nimport OuputNodeExpandedModal from 'components/molecules/modals/OutputNodeExpandedModal';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\n\nconst OutputNode = ({ id, data }) => {\n  const [outputExpandedModal, setOutputExpandedModal] = React.useState(false);\n\n  return (\n    <FlowNode\n      title='Output'\n      handleLeft={true}\n      handleLeftData={{ type: 'target' }}\n      handleRight={true}\n      handleRightData={{ type: 'source' }}\n    >\n      {data.output ? (\n        <button type='button' onClick={() => setOutputExpandedModal(true)}>\n          <Tippy content='Expand' placement='top'>\n            <ArrowsPointingOutIcon className='w-5 h-5' />\n          </Tippy>\n        </button>\n      ) : (\n        <></>\n      )}\n      <div className='text-xs text-gray-900 border border-gray-300 rounded-lg nodrag nowheel min-w-72 max-w-96 bg-gray-50 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'>\n        {data.output ? (\n          <Editor\n            name='output-text'\n            value={JSON.stringify(data.output, null, 2)}\n            readOnly={true}\n            classes={'w-full max-h-96'}\n          />\n        ) : (\n          <div className='p-2'>{'Run flow to see data'}</div>\n        )}\n      </div>\n      <OuputNodeExpandedModal\n        closeFn={() => setOutputExpandedModal(false)}\n        open={outputExpandedModal}\n        data={data.output}\n      />\n    </FlowNode>\n  );\n};\n\nOutputNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default OutputNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/RequestBody.js",
    "content": "import React, { Fragment, useState, useRef } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { EllipsisVerticalIcon, PlusIcon } from '@heroicons/react/24/solid';\nimport {\n  DocumentArrowUpIcon,\n  ClipboardDocumentCheckIcon,\n  ClipboardDocumentIcon,\n  TrashIcon,\n} from '@heroicons/react/24/outline';\n//import { Menu, Transition } from '@headlessui/react';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { toast } from 'react-toastify';\nimport { Editor } from 'components/atoms/Editor';\nimport NodeHorizontalDivider from 'components/atoms/flow/NodeHorizontalDivider';\nimport TextInputWithLabel from 'components/atoms/common/TextInputWithLabel';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES } from 'constants/Common';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { useTabStore } from 'stores/TabStore';\nimport { cloneDeep } from 'lodash';\nimport { CopyToClipboard } from 'react-copy-to-clipboard';\nimport Tippy from '@tippyjs/react';\nimport FormDataSelector from './FormDataSelector';\n\nconst requestBodyTypeOptions = ['None', 'form-data', 'raw-json'];\n\nconst RequestBody = ({ nodeId, nodeData }) => {\n  const { ipcRenderer } = window;\n  const setRequestNodeBody = useCanvasStore((state) => state.setRequestNodeBody);\n  const [cachedValues, setCachedValues] = React.useState({});\n\n  const [copyStatus, setCopyStatus] = useState(false); // To indicate if the text was copied\n\n  // Refs to hold the input elements\n  const inputRefs = useRef([]);\n\n  const updateCachedValues = () => {\n    if (nodeData.requestBody) {\n      if (nodeData.requestBody.type) {\n        setCachedValues({\n          ...cachedValues,\n          [nodeData.requestBody.type]: nodeData.requestBody.body,\n        });\n      }\n    }\n  };\n\n  const handleFileUpload = async (e, index) => {\n    if (!e.target.files) return;\n\n    if (e.target.files.length === 1) {\n      const file = e.target.files[0];\n      const { name, path } = file;\n\n      const reader = new FileReader();\n      reader.onload = (evt) => {\n        if (!evt?.target?.result) {\n          return;\n        }\n        const { result } = evt.target;\n\n        const value = result;\n\n        const updatedParams = [...nodeData.requestBody.body];\n        const activeCollectionId = useCanvasStore.getState().collectionId;\n        const activeCollection = useCollectionStore.getState().collections.find((c) => c.id === activeCollectionId);\n        if (activeCollection && path.startsWith(activeCollection.pathname)) {\n          updatedParams[index].value = ipcRenderer.relative(activeCollection.pathname, path);\n        } else {\n          updatedParams[index].value = path;\n        }\n\n        updatedParams[index].name = name;\n        setRequestNodeBody(nodeId, 'form-data', updatedParams);\n      };\n      reader.readAsDataURL(file);\n    }\n  };\n\n  const handleRawJson = (value) => {\n    setRequestNodeBody(nodeId, 'raw-json', value);\n  };\n\n  const handleClose = (option) => {\n    updateCachedValues();\n    if (option == 'None') {\n      setRequestNodeBody(nodeId, option, undefined);\n    } else if (option == 'raw-json') {\n      if (cachedValues['raw-json']) {\n        setRequestNodeBody(nodeId, option, cachedValues['raw-json']);\n      } else {\n        setRequestNodeBody(nodeId, option, '');\n      }\n    } else if (option == 'form-data') {\n      if (cachedValues['form-data']) {\n        setRequestNodeBody(nodeId, option, cachedValues['form-data']);\n      } else {\n        setRequestNodeBody(nodeId, option, []);\n      }\n    }\n  };\n\n  const getActiveVariables = () => {\n    const collectionId = useCanvasStore.getState().collectionId;\n    if (collectionId) {\n      const activeEnv = useCollectionStore\n        .getState()\n        .collections.find((c) => c.id === collectionId)\n        ?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);\n      if (activeEnv) {\n        return Object.keys(cloneDeep(activeEnv.variables));\n      }\n    }\n    return [];\n  };\n\n  const renderFormData = (params) => {\n    return (\n      <div>\n        {params && params.length > 0 ? (\n          <table className='border-collapse border-2 border-background-dark leading-normal'>\n            <thead>\n              <tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold tracking-wider'>\n                <th className='border-2 border-background-dark p-2'>Key</th>\n                <th className='border-2 border-background-dark p-2'>Value</th>\n                <th className='border-2 border-background-dark p-2'></th>\n              </tr>\n            </thead>\n            <tbody>\n              {params.map((param, index) => (\n                <tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>\n                  <td className='whitespace-no-wrap border-2 border-background-dark'>\n                    <input\n                      type='text'\n                      className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'\n                      name='variable-name'\n                      value={param.key}\n                      onChange={(event) => {\n                        const updatedParams = [...nodeData.requestBody.body];\n                        updatedParams[index].key = event.target.value;\n                        setRequestNodeBody(nodeId, 'form-data', updatedParams);\n                      }}\n                    />\n                  </td>\n                  <td className='whitespace-no-wrap border-2 border-background-dark'>\n                    {param.type === 'text' ? (\n                      <input\n                        type='text'\n                        className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'\n                        name='variable-name'\n                        value={param.value}\n                        onChange={(event) => {\n                          const updatedParams = [...nodeData.requestBody.body];\n                          updatedParams[index].value = event.target.value;\n                          setRequestNodeBody(nodeId, 'form-data', updatedParams);\n                        }}\n                      />\n                    ) : (\n                      <div className='nodrag nowheel w-full'>\n                        <Button\n                          btnType={BUTTON_TYPES.secondary}\n                          isDisabled={false}\n                          onClickHandle={() => {\n                            //uploadFileForRequestNode.current.click();\n                            if (inputRefs.current[index]) {\n                              inputRefs.current[index].click(); // Trigger the file input click\n                            }\n                          }}\n                          fullWidth={true}\n                        >\n                          <DocumentArrowUpIcon className='h-4 w-4 text-center' />\n                          <div\n                            className='max-w-xs overflow-hidden whitespace-nowrap'\n                            style={{ textOverflow: 'ellipsis' }}\n                          >\n                            {param.name && param.name.trim() !== '' ? param.name : 'Upload File'}\n                          </div>\n                          {/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}\n                          <div className='hidden'>\n                            <input\n                              type='file'\n                              id='file'\n                              ref={(el) => (inputRefs.current[index] = el)}\n                              onChange={(e) => handleFileUpload(e, index)}\n                            />\n                          </div>\n                        </Button>\n                      </div>\n                    )}\n                  </td>\n                  <td className='border-2 border-background-dark p-2'>\n                    <div className='flex items-center gap-4'>\n                      <div\n                        onClick={() => {\n                          const updatedParams = nodeData.requestBody.body.filter((_, i) => i !== index);\n                          setRequestNodeBody(nodeId, 'form-data', updatedParams);\n                        }}\n                        className='cursor-pointer'\n                      >\n                        <TrashIcon className='h-4 w-4' />\n                      </div>\n                    </div>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        ) : (\n          ''\n        )}\n      </div>\n    );\n  };\n\n  return (\n    <>\n      <div className='flex items-center justify-between bg-background p-4'>\n        <h3>Body</h3>\n        <Listbox\n          value={nodeData.requestBody?.type ? nodeData.requestBody.type : 'None'}\n          onChange={(selectedValue) => {\n            handleClose(selectedValue);\n          }}\n          className='text-xl'\n        >\n          <div>\n            <Listbox.Button className='relative flex cursor-default border-cyan-950 text-left'>\n              <EllipsisVerticalIcon className='h-4 w-4' aria-hidden='true' data-click-from='body-type-menu' />\n            </Listbox.Button>\n            <Transition\n              as={Fragment}\n              leave='transition ease-in duration-100'\n              leaveFrom='opacity-100'\n              leaveTo='opacity-0'\n            >\n              <Listbox.Options className='absolute z-50 mt-1 max-h-60 w-36 overflow-auto bg-white py-1 text-base focus:outline-none'>\n                {requestBodyTypeOptions.map((bodyTypeOption) => (\n                  <Listbox.Option\n                    key={bodyTypeOption}\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-7 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={bodyTypeOption}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block`}>{bodyTypeOption}</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-1 font-semibold'>\n                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                ))}\n              </Listbox.Options>\n            </Transition>\n          </div>\n        </Listbox>\n      </div>\n      {nodeData.requestBody && nodeData.requestBody.type === 'raw-json' && (\n        <>\n          <NodeHorizontalDivider />\n          <div className='bg-background p-4'>\n            <div className='nodrag nowheel w-full min-w-72'>\n              <div className='relative bg-background-lighter'>\n                <Editor\n                  name='request-body-json'\n                  onChange={(e) => handleRawJson(e)}\n                  value={nodeData.requestBody.body}\n                  classes={'w-full max-h-96'}\n                  completionOptions={getActiveVariables()}\n                />\n\n                <div className='absolute right-5 top-0 cursor-pointer text-slate-400 hover:text-cyan-900'>\n                  <CopyToClipboard\n                    text={nodeData.requestBody.body}\n                    onCopy={() => {\n                      setCopyStatus(true);\n                      setTimeout(() => setCopyStatus(false), 2000); // Reset status after 2 seconds\n                    }}\n                  >\n                    <button>\n                      {copyStatus ? (\n                        <Tippy content='Copied to Clipboard' placement='top'>\n                          <ClipboardDocumentCheckIcon className='h-6 w-6' />\n                        </Tippy>\n                      ) : (\n                        <Tippy content='Copy to Clipboard' placement='top'>\n                          <ClipboardDocumentIcon className='h-6 w-6' />\n                        </Tippy>\n                      )}\n                    </button>\n                  </CopyToClipboard>\n                </div>\n              </div>\n              <Button\n                btnType={BUTTON_TYPES.secondary}\n                classes={'mt-2'}\n                isDisabled={false}\n                fullWidth={true}\n                onClickHandle={() => {\n                  try {\n                    const bodyJson = JSON.parse(nodeData.requestBody.body);\n                    const prettyBodyJson = JSON.stringify(bodyJson, null, 2);\n                    setRequestNodeBody(nodeId, 'raw-json', prettyBodyJson);\n                  } catch (e) {\n                    toast.error('Unable to beautify. Invalid JSON format.');\n                  }\n                }}\n              >\n                Beautify\n              </Button>\n            </div>\n          </div>\n        </>\n      )}\n      {nodeData.requestBody && nodeData.requestBody.type === 'form-data' && (\n        <>\n          <NodeHorizontalDivider />\n          <div className='bg-background pb-2'>\n            <div>\n              <div className='flex items-center justify-between'>\n                <div className='p-2'>Add Param</div>\n                <FormDataSelector\n                  onSelectHandler={(type) => {\n                    const currentParams = nodeData.requestBody.body;\n                    const updatedParams = currentParams.concat([{ key: '', value: '', type }]);\n                    setRequestNodeBody(nodeId, 'form-data', updatedParams);\n                  }}\n                />\n              </div>\n              {renderFormData(nodeData.requestBody.body)}\n            </div>\n          </div>\n        </>\n      )}\n    </>\n  );\n};\n\nRequestBody.propTypes = {\n  nodeData: PropTypes.object.isRequired,\n};\n\nexport default RequestBody;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/RequestNode.js",
    "content": "import React, { useEffect, useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport RequestBody from './RequestBody';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport { getInputType } from 'utils/common';\nimport { PlusIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { getDefaultValue } from 'utils/common';\nimport AddVariableModal from 'components/molecules/modals/flow/AddVariableModal';\nimport useCanvasStore from 'stores/CanvasStore';\nimport TextInput from 'components/atoms/common/TextInput';\nimport NodeHorizontalDivider from 'components/atoms/flow/NodeHorizontalDivider';\nimport { Listbox, Transition } from '@headlessui/react';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport requestNodes from '../constants/requestNodes';\nimport { TextEditor } from 'components/atoms/common/TextEditor';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { useTabStore } from 'stores/TabStore';\nimport { cloneDeep } from 'lodash';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport { Tab } from '@headlessui/react';\n\nconst RequestNode = ({ id, data }) => {\n  const setRequestNodeUrl = useCanvasStore((state) => state.setRequestNodeUrl);\n  const setRequestNodeType = useCanvasStore((state) => state.setRequestNodeType);\n  const setRequestNodeHeaders = useCanvasStore((state) => state.setRequestNodeHeaders);\n  const requestNodeAddPreRequestVar = useCanvasStore((state) => state.requestNodeAddPreRequestVar);\n  const requestNodeDeletePreRequestVar = useCanvasStore((state) => state.requestNodeDeletePreRequestVar);\n  const requestNodeChangePreRequestVar = useCanvasStore((state) => state.requestNodeChangePreRequestVar);\n\n  const requestNodeAddPostResponseVar = useCanvasStore((state) => state.requestNodeAddPostResponseVar);\n  const requestNodeDeletePostResponseVar = useCanvasStore((state) => state.requestNodeDeletePostResponseVar);\n  const requestNodeChangePostResponseVar = useCanvasStore((state) => state.requestNodeChangePostResponseVar);\n\n  const [variableDialogOpen, setVariableDialogOpen] = useState(false);\n  const [modalType, setModalType] = useState('');\n\n  const handleAddVariable = (vType, name, type) => {\n    if (vType === 'pre-request') {\n      requestNodeAddPreRequestVar(id, name, type);\n    } else if (vType === 'post-response') {\n      requestNodeAddPostResponseVar(id, name, type);\n    }\n  };\n\n  const handleDeleteVariable = (event, vType, vId) => {\n    if (vType === 'pre-request') {\n      requestNodeDeletePreRequestVar(id, vId);\n    } else if (vType === 'post-response') {\n      requestNodeDeletePostResponseVar(id, vId);\n    }\n  };\n\n  const handleVariableChange = (event, vType, vId) => {\n    if (vType === 'pre-request') {\n      requestNodeChangePreRequestVar(id, vId, event.target.value);\n    } else if (vType === 'post-response') {\n      requestNodeChangePostResponseVar(id, vId, event.target.value);\n    }\n  };\n\n  const handleUrlInputChange = (value) => {\n    setRequestNodeUrl(id, value);\n  };\n\n  const Tooltip = ({ text }) => {\n    return (\n      <Tippy content={text} placement='top' maxWidth='none'>\n        <svg\n          tabIndex='-1'\n          id='tooltipId'\n          xmlns='http://www.w3.org/2000/svg'\n          width='14'\n          height='14'\n          fill='currentColor'\n          className='ml-2 inline-block cursor-pointer'\n          viewBox='0 0 16 16'\n          style={{ marginTop: 1 }}\n        >\n          <path d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z' />\n          <path d='M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z' />\n        </svg>\n      </Tippy>\n    );\n  };\n\n  const renderVariables = (vType) => {\n    const variables = vType === 'pre-request' ? data.preReqVars : data.postRespVars;\n    return (\n      <div>\n        {variables && Object.keys(variables).length > 0 ? (\n          <table className='border-collapse border-2 border-background-dark leading-normal'>\n            <thead>\n              <tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold tracking-wider'>\n                <th className='border-2 border-background-dark p-2'>Name</th>\n                <th className='border-2 border-background-dark p-2'>Value</th>\n                <th className='border-2 border-background-dark p-2'></th>\n              </tr>\n            </thead>\n            <tbody>\n              {Object.keys(variables).map((id, index) => (\n                <tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>\n                  <td className='whitespace-no-wrap border-2 border-background-dark'>\n                    <input\n                      type='text'\n                      className='nodrag nowheel block h-9 w-full bg-transparent p-2.5 outline-none'\n                      name='variable-name'\n                      value={id}\n                      readOnly\n                    />\n                  </td>\n                  <td className='whitespace-no-wrap border-2 border-background-dark'>\n                    {variables[id].type === 'Boolean' ? (\n                      <select\n                        onChange={(e) => handleVariableChange(e, vType, id)}\n                        name='boolean-val'\n                        className=' nodrag nowheel h-9 w-full bg-background-light p-2.5 px-1 outline-none'\n                        value={variables[id].value}\n                      >\n                        <option value='true'>True</option>\n                        <option value='false'>False</option>\n                      </select>\n                    ) : variables[id].type === 'Now' ? (\n                      <input\n                        type='text'\n                        className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'\n                        name='variable-name'\n                        value='Date.now()'\n                        readOnly\n                      />\n                    ) : (\n                      <input\n                        type={getInputType(variables[id].type)}\n                        className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'\n                        name='variable-value'\n                        data-type={getInputType(variables[id].type)}\n                        onChange={(e) => handleVariableChange(e, vType, id)}\n                        value={variables[id].value}\n                      />\n                    )}\n                  </td>\n                  <td className='border-2 border-background-dark p-2'>\n                    <div className='flex items-center gap-4'>\n                      <Tooltip text={variables[id].type} />\n                      <div onClick={(e) => handleDeleteVariable(e, vType, id)} className='cursor-pointer'>\n                        <TrashIcon className='h-4 w-4' />\n                      </div>\n                    </div>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        ) : (\n          ''\n        )}\n      </div>\n    );\n  };\n\n  const headerNameChange = (index, updateName) => {\n    const currentHeaders = useCanvasStore.getState().nodes.find((n) => n.id === id)?.data?.headers || [];\n    const updatedHeaders = currentHeaders.map((p, idx) => {\n      if (idx === index) {\n        return { name: updateName, value: p.value };\n      }\n      return p;\n    });\n    setRequestNodeHeaders(id, updatedHeaders);\n  };\n\n  const headerValueChange = (index, updateValue) => {\n    const currentHeaders = useCanvasStore.getState().nodes.find((n) => n.id === id)?.data?.headers || [];\n    const updatedHeaders = currentHeaders.map((p, idx) => {\n      if (idx === index) {\n        return { name: p.name, value: updateValue };\n      }\n      return p;\n    });\n    setRequestNodeHeaders(id, updatedHeaders);\n  };\n\n  const renderHeaders = () => {\n    return (\n      <div>\n        {data.headers && data.headers.length > 0 ? (\n          <table className='border-collapse border-2 border-background-dark leading-normal'>\n            <thead>\n              <tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold tracking-wider'>\n                <th className='border-2 border-background-dark p-2'>Name</th>\n                <th className='border-2 border-background-dark p-2'>Value</th>\n                <th className='border-2 border-background-dark p-2'></th>\n              </tr>\n            </thead>\n            <tbody>\n              {data.headers.map((pair, index) => (\n                <tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>\n                  <td className='whitespace-no-wrap w-[45%] border-2 border-background-dark'>\n                    <TextEditor\n                      placeHolder=''\n                      onChangeHandler={(name) => headerNameChange(index, name)}\n                      name='header-name'\n                      value={pair.name}\n                      completionOptions={getActiveVariables()}\n                      styles={'w-40 nodrag nowheel block bg-background-light p-2.5 outline-none'}\n                    />\n                  </td>\n                  <td className='whitespace-no-wrap w-[45%] border-2 border-background-dark'>\n                    <TextEditor\n                      placeHolder=''\n                      onChangeHandler={(value) => headerValueChange(index, value)}\n                      name='header-value'\n                      value={pair.value}\n                      completionOptions={getActiveVariables()}\n                      styles={'w-40 nodrag nowheel block bg-background-light p-2.5 outline-none'}\n                    />\n                  </td>\n                  <td className='w-[10%] border-2 border-background-dark p-2'>\n                    <div className='flex items-center gap-4'>\n                      {/* <Tooltip text={variables[id].type} /> */}\n                      <div\n                        onClick={(e) => {\n                          setRequestNodeHeaders(\n                            id,\n                            data.headers.filter((_, i) => i !== index),\n                          );\n                        }}\n                        className='cursor-pointer'\n                      >\n                        <TrashIcon className='h-4 w-4' />\n                      </div>\n                    </div>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        ) : (\n          ''\n        )}\n      </div>\n    );\n  };\n\n  const getActiveVariables = () => {\n    const collectionId = useCanvasStore.getState().collectionId;\n    if (collectionId) {\n      const activeEnv = useCollectionStore\n        .getState()\n        .collections.find((c) => c.id === collectionId)\n        ?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);\n      if (activeEnv) {\n        return Object.keys(cloneDeep(activeEnv.variables));\n      }\n    }\n    return [];\n  };\n\n  const listBox = () => {\n    return (\n      <Listbox\n        value={data.requestType}\n        onChange={(selectedValue) => {\n          setRequestNodeType(id, selectedValue);\n        }}\n        className='text-xl'\n      >\n        <div>\n          <Listbox.Button className='relative flex cursor-default border-cyan-950 text-left'>\n            <span className='block truncate'>{data.requestType}</span>\n            <span className='p-1'>\n              <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n            </span>\n          </Listbox.Button>\n          <Transition as={Fragment} leave='transition ease-in duration-100' leaveFrom='opacity-100' leaveTo='opacity-0'>\n            <Listbox.Options className='absolute z-50 mt-1 max-h-60 w-36 overflow-auto bg-white py-1 text-base focus:outline-none'>\n              {requestNodes\n                .map((el) => el.requestType)\n                .map((reqType) => (\n                  <Listbox.Option\n                    key={reqType}\n                    className={({ active }) =>\n                      `relative cursor-default select-none py-2 pl-7 pr-4 hover:font-semibold ${\n                        active ? 'bg-background-light text-slate-900' : ''\n                      }`\n                    }\n                    value={reqType}\n                  >\n                    {({ selected }) => (\n                      <>\n                        <span className={`block`}>{reqType}</span>\n                        {selected ? (\n                          <span className='absolute inset-y-0 left-0 flex items-center pl-1 font-semibold'>\n                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                          </span>\n                        ) : null}\n                      </>\n                    )}\n                  </Listbox.Option>\n                ))}\n            </Listbox.Options>\n          </Transition>\n        </div>\n      </Listbox>\n    );\n  };\n\n  const getDefaultIndex = () => {\n    if (data.requestBody?.type) {\n      return 0;\n    } else if (\n      (data.preReqVars && Object.entries(data.preReqVars).length > 0) ||\n      (data.postRespVars && Object.entries(data.postRespVars).length > 0)\n    ) {\n      return 1;\n    } else if (data.headers && data.headers.length > 0) {\n      return 2;\n    } else {\n      return 0;\n    }\n  };\n\n  return (\n    <FlowNode\n      title={listBox()}\n      handleLeft={true}\n      handleLeftData={{ type: 'target' }}\n      handleRight={true}\n      handleRightData={{ type: 'source' }}\n    >\n      <div className='w-96'>\n        <TextEditor\n          placeHolder={`Enter URL for a ${data.requestType} request`}\n          onChangeHandler={handleUrlInputChange}\n          name={'url'}\n          value={data.url ? data.url : ''}\n          completionOptions={getActiveVariables()}\n          styles={\n            'w-full mb-2 nodrag nowheel rounded block border border-slate-700 bg-background-light p-2.5 text-base outline-none'\n          }\n        />\n        <NodeHorizontalDivider />\n        <Tab.Group defaultIndex={getDefaultIndex()}>\n          <Tab.List className='flex'>\n            <Tab\n              className={({ selected }) =>\n                `w-full p-2 ${selected ? 'rounded border border-cyan-900 bg-slate-100' : ''}`\n              }\n            >\n              Body\n            </Tab>\n            <Tab\n              className={({ selected }) =>\n                `w-full p-2 ${selected ? 'rounded border border-cyan-900 bg-slate-100' : ''}`\n              }\n            >\n              Variables\n            </Tab>\n            <Tab\n              className={({ selected }) =>\n                `w-full p-2 ${selected ? 'rounded border border-cyan-900 bg-slate-100' : ''}`\n              }\n            >\n              Headers\n            </Tab>\n          </Tab.List>\n          <Tab.Panels className='mt-2'>\n            <Tab.Panel>\n              <RequestBody nodeId={id} nodeData={data} />\n            </Tab.Panel>\n            <Tab.Panel>\n              <div className='bg-background'>\n                <h3 className='p-2'>Variables</h3>\n                <div className='px-2'>\n                  <NodeHorizontalDivider />\n                  <div className='pb-2'>\n                    <div className='flex items-center justify-between'>\n                      <div className='p-2'>Pre Request</div>\n                      <button\n                        onClick={() => {\n                          setModalType('pre-request');\n                          setVariableDialogOpen(true);\n                        }}\n                      >\n                        <PlusIcon className='h-4 w-4' />\n                      </button>\n                    </div>\n                    {renderVariables('pre-request')}\n                  </div>\n                  <NodeHorizontalDivider />\n                  <div className='pb-2'>\n                    <div className='flex items-center justify-between'>\n                      <div className='p-2'>Post Response</div>\n                      <button\n                        onClick={() => {\n                          setModalType('post-response');\n                          setVariableDialogOpen(true);\n                        }}\n                      >\n                        <PlusIcon className='h-4 w-4' />\n                      </button>\n                    </div>\n                    {renderVariables('post-response')}\n                  </div>\n                </div>\n              </div>\n            </Tab.Panel>\n            <Tab.Panel>\n              <div className='bg-background'>\n                {/* <h3 className='p-2'>Headers</h3> */}\n                <div className='px-2'>\n                  <NodeHorizontalDivider />\n                  <div className='pb-2'>\n                    <div className='flex items-center justify-between'>\n                      <div className='p-2'>Headers</div>\n                      <button\n                        onClick={() => {\n                          const existingHeaders = data.headers || [];\n                          const updatedHeaders = existingHeaders.concat([{ name: '', value: '' }]);\n                          setRequestNodeHeaders(id, updatedHeaders);\n                        }}\n                      >\n                        <PlusIcon className='h-4 w-4' />\n                      </button>\n                    </div>\n                    {renderHeaders()}\n                  </div>\n                </div>\n              </div>\n            </Tab.Panel>\n          </Tab.Panels>\n        </Tab.Group>\n      </div>\n      <AddVariableModal\n        closeFn={() => setVariableDialogOpen(false)}\n        open={variableDialogOpen}\n        modalType={modalType}\n        onVariableAdd={handleAddVariable}\n      />\n    </FlowNode>\n  );\n};\n\nRequestNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default RequestNode;\n"
  },
  {
    "path": "src/components/molecules/flow/nodes/SetVarNode.js",
    "content": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport FlowNode from 'components/atoms/flow/FlowNode';\nimport { getInputType } from 'utils/common';\nimport { variableTypes } from 'components/molecules/modals/flow/AddVariableModal';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ } from 'constants/Common';\nimport EvaluateOperators from '../constants/evaluateOperators';\nimport TextInput from 'components/atoms/common/TextInput';\n\n// ToDo: Change standard select element(s) with headless list element\nconst SetVarNode = ({ id, data }) => {\n  const setVariableNodeName = useCanvasStore((state) => state.setVariableNodeName);\n  const setVariableNodeType = useCanvasStore((state) => state.setVariableNodeType);\n\n  const variableNodeChangeVar = useCanvasStore((state) => state.variableNodeChangeVar);\n  const setVariableNodeExpressionsVariable = useCanvasStore((state) => state.setVariableNodeExpressionsVariable);\n  const setVariableNodeExpressionOperator = useCanvasStore((state) => state.setVariableNodeExpressionOperator);\n\n  const renderVariable = () => {\n    return (\n      <div className='flex items-center justify-between pb-2'>\n        <div className='flex items-center justify-between text-sm border rounded outline-none cursor-default bg-background-light border-cyan-950'>\n          {data.variable.type === 'Boolean' ? (\n            <select\n              onChange={(e) => variableNodeChangeVar(id, e.target.value)}\n              name='boolean-val'\n              className='nodrag h-9 w-full bg-transparent p-2.5 px-1 outline-none '\n              value={data.variable.value}\n            >\n              <option value='true'>True</option>\n              <option value='false'>False</option>\n            </select>\n          ) : data.variable.type === 'Now' ? (\n            <div></div>\n          ) : (\n            <input\n              type={getInputType(data.variable.type)}\n              className='nodrag nowheel block h-9 w-full bg-transparent p-2.5 outline-none '\n              name='variable-value'\n              data-type={getInputType(data.variable.type)}\n              onChange={(e) => variableNodeChangeVar(id, e.target.value)}\n              value={data.variable.value}\n            />\n          )}\n        </div>\n      </div>\n    );\n  };\n\n  const operatorMenu = (id, data) => {\n    const handleOperatorSelection = (event) => {\n      const selectedValue = event.target?.value;\n      // ToDO: verify the behavior when use selects the default item\n      if (selectedValue !== CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value) {\n        setVariableNodeExpressionOperator(id, selectedValue);\n      }\n    };\n\n    return (\n      <div className='mb-4'>\n        <select\n          onChange={handleOperatorSelection}\n          name='operator-type'\n          value={\n            data.variable && data.variable.value.operator\n              ? data.variable.value.operator\n              : CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value\n          }\n          className='w-full h-12 p-2 border rounded outline-none cursor-default bg-background-light border-cyan-950'\n        >\n          <option value={CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value}>\n            {CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.displayValue}\n          </option>\n          {Object.entries(EvaluateOperators).map(([key, value], index) => (\n            <option key={key} value={value}>\n              {value}\n            </option>\n          ))}\n        </select>\n      </div>\n    );\n  };\n\n  const variableElem = (id, data, varName) => {\n    const handleInputTypeSelection = (event) => {\n      const selectedValue = event.target?.value;\n      switch (selectedValue) {\n        case 'String':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, '');\n          break;\n        case 'Select':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, '');\n          break;\n        case 'Variable':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, '');\n          break;\n        case 'Number':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, 0);\n          break;\n        case 'Boolean':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, false);\n          break;\n        case 'Now':\n          setVariableNodeExpressionsVariable(id, varName, selectedValue, '');\n          break;\n      }\n    };\n\n    return (\n      <div className='flex items-center justify-center mb-4 text-sm border rounded outline-none cursor-default bg-background-light border-cyan-950'>\n        {data.variable && data.variable.value.variables && data.variable.value.variables[varName] ? (\n          data.variable.value.variables[varName].type === 'Boolean' ? (\n            <select\n              onChange={(event) => setVariableNodeExpressionsVariable(id, varName, 'Boolean', event.target?.value)}\n              name='boolean-val'\n              className='nodrag h-12 w-full bg-transparent p-2.5 px-1 outline-none'\n              value={data.variable.value.variables[varName].value}\n            >\n              <option value='true'>True</option>\n              <option value='false'>False</option>\n            </select>\n          ) : data.variable.value.variables[varName].type === 'Now' ? (\n            <div></div>\n          ) : (\n            <input\n              id='outlined-adornment-weight'\n              type={getInputType(data.variable.value.variables[varName].type)}\n              className='nodrag nowheel block h-12 w-full bg-transparent p-2.5 outline-none'\n              name='variable-value'\n              placeholder={varName}\n              value={data.variable.value.variables[varName].value}\n              onChange={(event) => {\n                const updatedValue = event.target.value;\n                switch (data.variable.value.variables[varName].type) {\n                  case 'String':\n                    setVariableNodeExpressionsVariable(id, varName, 'String', updatedValue.toString());\n                    break;\n                  case 'Select':\n                    setVariableNodeExpressionsVariable(id, varName, 'Select', updatedValue.toString());\n                    break;\n                  case 'Variable':\n                    setVariableNodeExpressionsVariable(id, varName, 'Variable', updatedValue.toString());\n                    break;\n                  case 'Number':\n                    setVariableNodeExpressionsVariable(id, varName, 'Number', parseInt(updatedValue));\n                    break;\n                }\n              }}\n            />\n          )\n        ) : (\n          <input\n            id='outlined-adornment-weight'\n            type='text'\n            className='nodrag nowheel block h-12 w-full bg-transparent p-2.5 outline-none'\n            name='variable-value'\n            placeholder={varName}\n            value=''\n            onChange={(event) => {\n              // default type is string, as soon as we select another type, it goes to above flow\n              const updatedValue = event.target.value;\n              setVariableNodeExpressionsVariable(id, varName, 'String', updatedValue.toString());\n            }}\n          />\n        )}\n\n        <select\n          onChange={handleInputTypeSelection}\n          name='var-input-type'\n          className='w-full h-8 p-0 px-1 bg-transparent border-l outline-none nodrag border-cyan-950'\n          value={\n            data.variable && data.variable.value.variables && data.variable.value.variables[varName]\n              ? data.variable.value.variables[varName].type\n              : 'String'\n          }\n        >\n          <option value='Select'>Select</option>\n          <option value='String'>String</option>\n          <option value='Variable'>Variable</option>\n          <option value='Number'>Number</option>\n          <option value='Boolean'>Boolean</option>\n          <option value='Now'>Now</option>\n        </select>\n      </div>\n    );\n  };\n\n  return (\n    <FlowNode\n      title={'Set Variable'}\n      handleLeft={true}\n      handleLeftData={{ type: 'target' }}\n      handleRight={true}\n      handleRightData={{ type: 'source' }}\n    >\n      <div className='flex flex-col gap-4'>\n        <TextInput\n          placeHolder={`Enter variable name`}\n          onChangeHandler={(e) => setVariableNodeName(id, e.target.value)}\n          name={'variable_name'}\n          value={data.variable && data.variable.name ? data.variable.name : ''}\n        />\n        <select\n          onChange={(e) => setVariableNodeType(id, e.target.value)}\n          name='var-input-type'\n          className='w-full p-2 border rounded outline-none cursor-default bg-background-light border-cyan-950'\n          value={data.variable && data.variable.type ? data.variable.type : ''}\n        >\n          <option value=''>None</option>\n          {variableTypes.map((option) => (\n            <option key={option.value} value={option.value}>\n              {option.label}\n            </option>\n          ))}\n          <option value='Expression'>Expression</option>\n        </select>\n        {data.variable && data.variable.type ? (\n          data.variable.type === 'Expression' ? (\n            <div>\n              <div>{variableElem(id, data, 'var1')}</div>\n              <div>{operatorMenu(id, data)}</div>\n              <div>{variableElem(id, data, 'var2')}</div>\n            </div>\n          ) : data.variable.type === '' ? (\n            <div></div>\n          ) : (\n            <div>\n              <div>{renderVariable()}</div>\n            </div>\n          )\n        ) : (\n          <div></div>\n        )}\n      </div>\n    </FlowNode>\n  );\n};\n\nSetVarNode.propTypes = {\n  data: PropTypes.object.isRequired,\n};\n\nexport default SetVarNode;\n"
  },
  {
    "path": "src/components/molecules/flow/utils.js",
    "content": "import { isEqual, reduce, map } from 'lodash';\nimport requestNodes from './constants/requestNodes';\n\nexport const orderNodesByTags = (nodes, filter) => {\n  const result = {};\n  let filterNodes = nodes.filter(\n    (node) => node.requestType && requestNodes.map((req) => req.requestType).includes(node.requestType),\n  );\n  if (filter.trim() != '') {\n    filterNodes = nodes.filter(\n      (n) =>\n        (n.operationId && n.operationId.toLowerCase().includes(filter.toLowerCase())) ||\n        (n.description && n.description.toLowerCase().includes(filter.toLowerCase())),\n    );\n  }\n  if (filterNodes) {\n    filterNodes.map((node) => {\n      if (node.tags) {\n        node.tags.map((tag) => {\n          if (!result[tag]) {\n            result[tag] = [];\n          }\n          result[tag].push(node);\n        });\n      } else {\n        if (!result['no_tag']) {\n          result['no_tag'] = [];\n        }\n        result['no_tag'].push(node);\n      }\n    });\n  }\n  return Object.keys(result)\n    .sort()\n    .reduce((obj, key) => {\n      obj[key] = result[key];\n      return obj;\n    }, {});\n};\n\nexport const compareTwoObjects = function (a, b) {\n  var result = {\n    different: [],\n    missing_from_first: [],\n    missing_from_second: [],\n  };\n\n  reduce(\n    a,\n    function (result, value, key) {\n      if (Object.prototype.hasOwnProperty.call(b, key)) {\n        if (isEqual(value, b[key])) {\n          return result;\n        } else {\n          if (typeof a[key] != typeof {} || typeof b[key] != typeof {}) {\n            //dead end.\n            result.different.push(key);\n            return result;\n          } else {\n            var deeper = compareTwoObjects(a[key], b[key]);\n            result.different = result.different.concat(\n              map(deeper.different, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n\n            result.missing_from_second = result.missing_from_second.concat(\n              map(deeper.missing_from_second, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n\n            result.missing_from_first = result.missing_from_first.concat(\n              map(deeper.missing_from_first, (sub_path) => {\n                return key + '.' + sub_path;\n              }),\n            );\n            return result;\n          }\n        }\n      } else {\n        result.missing_from_second.push(key);\n        return result;\n      }\n    },\n    result,\n  );\n\n  reduce(\n    b,\n    function (result, value, key) {\n      if (Object.prototype.hasOwnProperty.call(a, key)) {\n        return result;\n      } else {\n        result.missing_from_first.push(key);\n        return result;\n      }\n    },\n    result,\n  );\n\n  return result;\n};\n\nexport const initFlowData = {\n  nodes: [\n    {\n      id: '0',\n      type: 'startNode',\n      position: {\n        x: 150,\n        y: 150,\n      },\n      deletable: false,\n      width: 90,\n      height: 60,\n    },\n    {\n      id: '1',\n      type: 'authNode',\n      description: 'Define authentication for the requests',\n      data: {\n        type: 'no-auth',\n      },\n      position: {\n        x: 400,\n        y: 150,\n      },\n      width: 147,\n      height: 107,\n    },\n  ],\n  edges: [\n    {\n      id: 'reactflow__edge-0-1',\n      source: '0',\n      sourceHandle: null,\n      target: '1',\n      targetHandle: null,\n      type: 'buttonedge',\n    },\n  ],\n  viewport: { x: 0, y: 0, zoom: 1 },\n};\n\nexport const timeoutForGraphRun = [\n  { value: '60000', label: '1 minute' },\n  { value: '300000', label: '5 minutes' },\n  { value: '600000', label: '10 minutes' },\n  { value: '900000', label: '15 minutes' },\n  { value: '1800000', label: '30 minutes' },\n];\n"
  },
  {
    "path": "src/components/molecules/footers/MainFooter.js",
    "content": "import React, { useState } from 'react';\nimport {\n  ArrowLeftEndOnRectangleIcon,\n  ArrowRightStartOnRectangleIcon,\n  Cog8ToothIcon,\n} from '@heroicons/react/24/outline';\n\nimport Tippy from '@tippyjs/react';\nimport useNavigationStore from 'stores/AppNavBarStore';\nimport useCollectionStore from 'stores/CollectionStore';\nimport SettingsModal from '../modals/SettingsModal';\n\nconst MainFooter = () => {\n  const collections = useCollectionStore((state) => state.collections);\n  const isNavBarCollapsed = useNavigationStore((state) => state.collapseNavBar);\n  const updateNavCollapseState = useNavigationStore((state) => state.setNavCollapseState);\n  const [openSettingsModal, setOpenSettingsModal] = useState(false);\n  return (\n    <footer className='flex items-center justify-between px-4 py-3 text-xs'>\n      <div className='flex items-center justify-between gap-2'>\n        <button onClick={() => setOpenSettingsModal(true)}>\n          <Cog8ToothIcon className='w-6 h-6' />\n        </button>\n        <label className='py-1 cursor-pointer swap swap-rotate'>\n          <input\n            // Overriding styles as Daisy UI input and React Hook form's input were conflicting\n            className='!focus:outline-none !focus:ring-0 !appearance-none !border-0 !bg-transparent !bg-none !outline-none'\n            type='checkbox'\n            onClick={(event) => {\n              if (collections.length) {\n                // since default is false for isNavBarCollapsed\n                updateNavCollapseState(!isNavBarCollapsed);\n              } else {\n                event.preventDefault();\n                event.stopPropagation();\n              }\n            }}\n          />\n          <ArrowRightStartOnRectangleIcon className='w-6 h-6 swap-on' />\n          <ArrowLeftEndOnRectangleIcon className='w-6 h-6 swap-off' />\n        </label>\n      </div>\n      <div className='flex items-center justify-between gap-4 font-semibold'>\n        <Tippy content='External Link' placement='top'>\n          <a href='https://github.com/FlowTestAI/FlowTest' target='_blank' rel='noreferrer' className='link'>\n            Github\n          </a>\n        </Tippy>\n\n        <Tippy content='External Link' placement='top'>\n          <a\n            href='https://github.com/FlowTestAI/FlowTest/discussions'\n            target='_blank'\n            rel='noreferrer'\n            className='link'\n          >\n            Contact us\n          </a>\n        </Tippy>\n\n        <Tippy content='External Link' placement='top'>\n          <a href='https://discord.gg/Pf9tdSjPeF' target='_blank' rel='noreferrer' className='link'>\n            Discord\n          </a>\n        </Tippy>\n\n        <Tippy content='Coming Soon' placement='top'>\n          <a href='#' className='link'>\n            Docs\n          </a>\n        </Tippy>\n\n        <Tippy content='External Link' placement='top'>\n          <a href='https://github.com/FlowTestAI/FlowTest' target='_blank' rel='noreferrer' className='link'>\n            About\n          </a>\n        </Tippy>\n      </div>\n      <SettingsModal closeFn={() => setOpenSettingsModal(false)} open={openSettingsModal} />\n    </footer>\n  );\n};\n\nexport default MainFooter;\n"
  },
  {
    "path": "src/components/molecules/headers/MainHeader.js",
    "content": "import React from 'react';\nimport ThemeController from 'components/atoms/ThemeController';\nimport AppLogo from 'components/atoms/Logo';\nimport useSettingsStore from 'stores/SettingsStore';\nimport { ExclamationCircleIcon } from '@heroicons/react/24/outline';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\n\nconst MainHeader = () => {\n  const appVersion = useSettingsStore((state) => state.appVersion);\n\n  return (\n    // <header className='flex items-center justify-between px-4 py-3 font-semibold tracking-widest dark:bg-slate-800 dark:text-white'>\n    <header className='flex items-center justify-between px-4 py-3 font-semibold tracking-widest'>\n      <div className='flex items-center gap-3'>\n        <AppLogo styleClasses='w-6 h-6' />\n        <div className='flex items-baseline gap-1'>\n          <h2>FlowTestAI</h2>\n          <span className='text-xs font-normal'>\n            <a href='https://github.com/FlowTestAI/FlowTest/releases' target='_blank' rel='noreferrer' className='link'>\n              {`v${appVersion.current}`}\n            </a>\n          </span>\n          {appVersion.current != appVersion.latest ? (\n            <span className='text-xs font-normal'>\n              <Tippy content={`v${appVersion.latest} available`} placement='top'>\n                <ExclamationCircleIcon className='w-5 h-5' />\n              </Tippy>\n            </span>\n          ) : (\n            <></>\n          )}\n        </div>\n      </div>\n      <ThemeController />\n    </header>\n  );\n};\n\nexport default MainHeader;\n"
  },
  {
    "path": "src/components/molecules/headers/SideBarHeader.js",
    "content": "import React from 'react';\nimport { UserIcon } from '@heroicons/react/20/solid';\nimport { ChevronRightIcon } from '@heroicons/react/16/solid';\nimport { AppNavBarItems } from 'constants/AppNavBar';\nimport useNavigationStore from 'stores/AppNavBarStore';\n\nconst SideBarHeader = () => {\n  const navigationSelectedValue = useNavigationStore((state) => {\n    const navigationVal = state.selectedNavVal;\n    switch (navigationVal) {\n      case AppNavBarItems.collections.value:\n        return AppNavBarItems.collections.displayValue;\n      case AppNavBarItems.environments.value:\n        return AppNavBarItems.environments.displayValue;\n      default:\n        return AppNavBarItems.environments.displayValue;\n    }\n  });\n  return (\n    <div className='flex justify-between p-2'>\n      <div className='flex items-center justify-start gap-1'>\n        <a className='flex items-center justify-between gap-1 link-hover link'>\n          <UserIcon className='w-3 h-3' />\n          <span>My Workspace</span>\n        </a>\n        <ChevronRightIcon className='w-3 h-3' />\n        <a className='link-hover link'>{navigationSelectedValue}</a>\n      </div>\n    </div>\n  );\n};\n\nexport default SideBarHeader;\n"
  },
  {
    "path": "src/components/molecules/headers/SideBarSubHeader.js",
    "content": "import React, { useState } from 'react';\nimport { FolderArrowDownIcon } from '@heroicons/react/24/outline';\nimport { PlusIcon } from '@heroicons/react/20/solid';\nimport ImportCollectionModal from 'components/molecules/modals/ImportCollectionModal';\nimport OpenCollectionModal from '../modals/OpenCollectionModal';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES } from 'constants/Common';\n\nconst SideBarSubHeader = () => {\n  const [openCollectionModal, setOpenCollectionModal] = useState(false);\n  const [importCollectionModal, setImportCollectionModal] = useState(false);\n  return (\n    <>\n      <div className='flex items-center flex-1'>\n        <Button\n          btnType={BUTTON_TYPES.tertiary}\n          classes={'rounded-none'}\n          isDisabled={false}\n          onClickHandle={() => setOpenCollectionModal(true)}\n          fullWidth={true}\n        >\n          <FolderArrowDownIcon className='w-4 h-4' />\n          <span className='font-semibold'>Open</span>\n        </Button>\n        <Button\n          btnType={BUTTON_TYPES.tertiary}\n          classes={'rounded-none'}\n          isDisabled={false}\n          onClickHandle={() => setImportCollectionModal(true)}\n          fullWidth={true}\n        >\n          <PlusIcon className='w-4 h-4' />\n          <span className='font-semibold'>Create</span>\n        </Button>\n      </div>\n      <div>\n        {/* ToDo: These modals will be contextual in term of selected folder or collection */}\n        <OpenCollectionModal closeFn={() => setOpenCollectionModal(false)} open={openCollectionModal} />\n        <ImportCollectionModal closeFn={() => setImportCollectionModal(false)} open={importCollectionModal} />\n      </div>\n    </>\n  );\n};\n\nexport default SideBarSubHeader;\n"
  },
  {
    "path": "src/components/molecules/headers/TabPanelHeader.js",
    "content": "import React, { useState } from 'react';\nimport { SparklesIcon, DocumentTextIcon } from '@heroicons/react/24/outline';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport SaveFlowModal from '../modals/SaveFlowModal';\nimport { useTabStore } from 'stores/TabStore';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES, OBJ_TYPES } from 'constants/Common';\nimport GenerateFlowTestModal from '../modals/GenerateFlowTestModal';\nimport useCanvasStore from 'stores/CanvasStore';\nimport SlidingPane from 'react-sliding-pane';\nimport 'react-sliding-pane/dist/react-sliding-pane.css';\nimport TimeoutSelector from 'components/atoms/common/TimeoutSelector';\nimport { timeoutForGraphRun } from 'components/molecules/flow/utils';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\n// import { JsonView, allExpanded, collapseAllNested, darkStyles, defaultStyles } from 'react-json-view-lite';\nimport 'react-json-view-lite/dist/index.css';\n// import { LogLevel } from '../flow/graph/GraphLogger';\n\nimport GenAIUsageDisclaimer from '../modals/GenAIUsageDisclaimer';\nimport useSettingsStore from 'stores/SettingsStore';\nimport FlowLogs from '../sideSheets/FlowLogs';\n\nconst TabPanelHeader = () => {\n  const focusTabId = useTabStore((state) => state.focusTabId);\n  const tabs = useTabStore((state) => state.tabs);\n  const focusTab = tabs.find((t) => t.id === focusTabId);\n\n  const setTimeout = useCanvasStore((state) => state.setTimeout);\n\n  const [slidingPaneState, setSlidingPaneState] = useState({\n    isPaneOpen: false,\n    isPaneOpenLeft: false,\n    title: 'Not available',\n    subtitle: 'Not Available',\n  });\n\n  const [genAiUsageDisclaimerModalOpen, setGenAiUsageDisclaimerModalOpen] = useState(false);\n  const [generateFlowTestModalOpen, setGenerateFlowTestModalOpen] = useState(false);\n\n  return (\n    <>\n      {focusTab ? (\n        <>\n          <div className='flex items-center justify-between px-4 py-3'>\n            <div className='py-3 text-base tracking-[0.15em]'>{focusTab.name}</div>\n\n            <div className='flex items-center justify-between gap-4 pl-4 border-l border-gray-300'>\n              {focusTab.type === OBJ_TYPES.flowtest && (\n                // ToDo: Check this\n                <div className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light px-4 py-2.5 text-cyan-900 transition hover:bg-background'>\n                  <TimeoutSelector\n                    optionsData={timeoutForGraphRun}\n                    onSelectHandler={(timeValue) => {\n                      setTimeout(timeValue);\n                    }}\n                  />\n                </div>\n              )}\n\n              <div className='flex items-center justify-center h-12'>\n                <SaveFlowModal tab={focusTab} />\n              </div>\n              {focusTab.type === OBJ_TYPES.flowtest && focusTab.run.logs && focusTab.run.logs.length != 0 ? (\n                <div>\n                  <Button\n                    id='graph-logs-side-sheet'\n                    btnType={BUTTON_TYPES.secondary}\n                    isDisabled={false}\n                    onClickHandle={() =>\n                      setSlidingPaneState({\n                        isPaneOpen: true,\n                        isPaneOpenLeft: false,\n                      })\n                    }\n                    fullWidth={true}\n                    onlyIcon={true}\n                    padding={'px-4 py-2.5'}\n                  >\n                    <Tippy content='Logs' placement='top'>\n                      <label htmlFor='graph-logs-side-sheet'>\n                        <DocumentTextIcon className='w-5 h-5' />\n                      </label>\n                    </Tippy>\n                  </Button>\n                  <SlidingPane\n                    className='side-sheet'\n                    overlayClassName='side-sheet-overlay'\n                    isOpen={slidingPaneState.isPaneOpen}\n                    title={focusTab.name}\n                    width='45%'\n                    onRequestClose={() => {\n                      // triggered on \"<\" on left top click or on outside click\n                      setSlidingPaneState({\n                        isPaneOpen: false,\n                        isPaneOpenLeft: false,\n                        title: 'closed',\n                        subtitle: 'closed',\n                      });\n                    }}\n                  >\n                    <FlowLogs logsData={focusTab}></FlowLogs>\n                  </SlidingPane>\n                </div>\n              ) : (\n                <></>\n              )}\n              {focusTab.type === OBJ_TYPES.flowtest && (\n                <div className='gen_ai_button'>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    isDisabled={false}\n                    onClickHandle={() => {\n                      if (useSettingsStore.getState().genAIUsageDisclaimer === true) {\n                        setGenerateFlowTestModalOpen(true);\n                      } else {\n                        setGenAiUsageDisclaimerModalOpen(true);\n                      }\n                    }}\n                    fullWidth={true}\n                    className='flex items-center justify-between gap-x-4'\n                  >\n                    <SparklesIcon className='w-5 h-5' />\n                    Generate\n                  </Button>\n                  <GenerateFlowTestModal\n                    closeFn={() => setGenerateFlowTestModalOpen(false)}\n                    open={generateFlowTestModalOpen}\n                    collectionId={focusTab.collectionId}\n                  />\n                  <GenAIUsageDisclaimer\n                    closeFn={() => setGenAiUsageDisclaimerModalOpen(false)}\n                    open={genAiUsageDisclaimerModalOpen}\n                    openGenerateFlowTestModal={() => setGenerateFlowTestModalOpen(true)}\n                  />\n                </div>\n              )}\n            </div>\n          </div>\n          <HorizontalDivider />\n        </>\n      ) : (\n        ''\n      )}\n    </>\n  );\n};\n\nTabPanelHeader.propTypes = {};\n\nexport default TabPanelHeader;\n"
  },
  {
    "path": "src/components/molecules/headers/WorkspaceHeader.js",
    "content": "import React, { useState } from 'react';\nimport { PlusIcon } from '@heroicons/react/20/solid';\nimport Tabs from '../../atoms/Tabs';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport SelectEnvironment from 'components/atoms/SelectEnvironment';\nimport { useTabStore } from 'stores/TabStore';\nimport useCollectionStore from 'stores/CollectionStore';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES, BUTTON_INTENT_TYPES } from 'constants/Common';\nimport NewFlowTestModal from '../modals/flow/NewFlowTestModal';\n\nconst WorkspaceHeader = () => {\n  const collections = useCollectionStore((state) => state.collections);\n  const focusTabId = useTabStore((state) => state.focusTabId);\n  const focusTab = useTabStore.getState().tabs.find((t) => t.id === focusTabId);\n  const environmentData = focusTab ? collections.find((c) => c.id === focusTab.collectionId)?.environments : [];\n  const [newFlowTestModal, setNewFlowTestModal] = useState(false);\n\n  return (\n    <>\n      <div className='flex items-center justify-between pr-4 min-h-12'>\n        <div className='flex items-center overflow-x-auto'>\n          <Tabs />\n          {collections.length != 0 ? (\n            <div className='inline-flex items-center justify-center hover:bg-background-light whitespace-nowrap'>\n              <Button\n                btnType={BUTTON_TYPES.tertiary}\n                classes={'min-h-12'}\n                isDisabled={false}\n                onClickHandle={() => setNewFlowTestModal(true)}\n                fullWidth={true}\n              >\n                <PlusIcon className='w-5 h-5 outline-none' />\n              </Button>\n            </div>\n          ) : (\n            <></>\n          )}\n        </div>\n        <SelectEnvironment environments={environmentData} />\n      </div>\n      <NewFlowTestModal closeFn={() => setNewFlowTestModal(false)} open={newFlowTestModal} />\n    </>\n  );\n};\n\nexport default WorkspaceHeader;\n"
  },
  {
    "path": "src/components/molecules/modals/AddEnvVariableModal.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport useEnvStore from 'stores/EnvStore';\nimport { toast } from 'react-toastify';\nimport TextInput from 'components/atoms/common/TextInput';\n\nconst AddEnvVariableModal = ({ closeFn = () => null, open = false, handleAddVariable }) => {\n  const [key, setKey] = useState('');\n  const [value, setValue] = useState('');\n\n  const [showKeyError, setShowKeyError] = useState(false);\n  const [showValueError, setShowValueError] = useState(false);\n\n  const resetFields = () => {\n    setKey('');\n    setValue('');\n    setShowKeyError(false);\n    setShowValueError(false);\n  };\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog\n        as='div'\n        className='relative z-10'\n        onClose={() => {\n          resetFields();\n          closeFn();\n        }}\n      >\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Add Variable\n                </Dialog.Title>\n                <div className='mt-6'>\n                  <div className='mt-4'>\n                    <TextInput\n                      id='key'\n                      placeHolder={`Key`}\n                      onChangeHandler={(event) => setKey(event.target.value)}\n                      name={'Key'}\n                      value={key}\n                    />\n                    {showKeyError ? <div className='py-2 text-red-600'>Please provide a key</div> : ''}\n                  </div>\n                  <div className='mt-4'>\n                    <TextInput\n                      id='value'\n                      placeHolder={`Value`}\n                      onChangeHandler={(event) => setValue(event.target.value)}\n                      name={'Value'}\n                      value={value}\n                    />\n                    {showValueError ? <div className='py-2 text-red-600'>Please provide a key</div> : ''}\n                  </div>\n                </div>\n                <div className='flex items-center gap-2 mt-6'>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    onClickHandle={() => {\n                      resetFields();\n                      closeFn();\n                    }}\n                    fullWidth={true}\n                  >\n                    Cancel\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    isDisabled={false}\n                    fullWidth={true}\n                    onClickHandle={() => {\n                      if (!key || key === '') {\n                        setShowKeyError(true);\n                        return;\n                      }\n                      if (!value || value === '') {\n                        setShowValueError(true);\n                        return;\n                      }\n                      const variables = useEnvStore.getState().variables;\n                      if (key.trim() === '') {\n                        toast.error('Variable name cannot be empty.');\n                      } else if (variables[key] != undefined) {\n                        toast.error('A variable with the same name already exists.');\n                      } else {\n                        handleAddVariable(key, value);\n                        //reset\n                        setKey('');\n                        setValue('');\n                      }\n                      resetFields();\n                      closeFn();\n                    }}\n                  >\n                    Add\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nAddEnvVariableModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\n\nexport default AddEnvVariableModal;\n"
  },
  {
    "path": "src/components/molecules/modals/ConfirmActionModal.js",
    "content": "import React, { Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES, BUTTON_INTENT_TYPES } from 'constants/Common';\n\nconst ConfirmActionModal = ({\n  message,\n  actionFn = () => null,\n  closeFn = () => null,\n  open = false,\n  closeModal,\n  leftButtonMessage = 'Cancel',\n  rightButtonMessage = 'Continue',\n}) => {\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeModal || closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Are you sure?\n                </Dialog.Title>\n                <div className='mt-6'>\n                  <div>{message}</div>\n                </div>\n                <div className='flex items-center gap-2 mt-6'>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    intentType={BUTTON_INTENT_TYPES.success}\n                    isDisabled={false}\n                    onClickHandle={closeFn}\n                    fullWidth={true}\n                  >\n                    {leftButtonMessage}\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    fullWidth={true}\n                    onClickHandle={actionFn}\n                  >\n                    {rightButtonMessage}\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nConfirmActionModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n  message: PropTypes.string.isRequired,\n  actionFn: PropTypes.func.isRequired,\n};\n\nexport default ConfirmActionModal;\n"
  },
  {
    "path": "src/components/molecules/modals/EditEnvVariableModal.js",
    "content": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport TextInput from 'components/atoms/common/TextInput';\n\nconst EditEnvVariableModal = ({ closeFn = () => null, open = false, editKey, editValue, handleAddVariable }) => {\n  const [value, setValue] = useState(editValue);\n\n  useEffect(() => {\n    setValue(editValue);\n  }, [editValue]);\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Edit Variable\n                </Dialog.Title>\n                <div className='mt-6'>\n                  <div className='mt-4'>\n                    <TextInput id='value' placeHolder={editKey} name={'Value'} value={editKey} disableState={true} />\n                  </div>\n                  <div className='mt-4'>\n                    <TextInput\n                      id='value'\n                      placeHolder={`Value`}\n                      onChangeHandler={(event) => setValue(event.target.value)}\n                      name={'Value'}\n                      value={value}\n                    />\n                  </div>\n                </div>\n                <div className='flex items-center gap-2 mt-6'>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    onClickHandle={closeFn}\n                    fullWidth={true}\n                  >\n                    Cancel\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    isDisabled={false}\n                    fullWidth={true}\n                    onClickHandle={() => {\n                      handleAddVariable(editKey, value);\n                      closeFn();\n                    }}\n                  >\n                    Edit\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nEditEnvVariableModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\n\nexport default EditEnvVariableModal;\n"
  },
  {
    "path": "src/components/molecules/modals/GenAIUsageDisclaimer.js",
    "content": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport { addGenAIUsageDisclaimer } from 'service/settings';\n\nconst GenAIUsageDisclaimer = ({ closeFn = () => null, open = false, openGenerateFlowTestModal = () => null }) => {\n  return (\n    <>\n      <Transition appear show={open} as={Fragment}>\n        <Dialog\n          as='div'\n          className='relative z-10'\n          onClose={() => {\n            closeFn();\n          }}\n        >\n          <Transition.Child\n            as={Fragment}\n            enter='ease-out duration-300'\n            enterFrom='opacity-0'\n            enterTo='opacity-100'\n            leave='ease-in duration-200'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            <div className='fixed inset-0 bg-black/25' />\n          </Transition.Child>\n\n          <div className='fixed inset-0 overflow-y-auto'>\n            <div className='flex min-h-full items-center justify-center p-4 text-center'>\n              <Transition.Child\n                as={Fragment}\n                enter='ease-out duration-300'\n                enterFrom='opacity-0 scale-95'\n                enterTo='opacity-100 scale-100'\n                leave='ease-in duration-200'\n                leaveFrom='opacity-100 scale-100'\n                leaveTo='opacity-0 scale-95'\n              >\n                <Dialog.Panel className='w-full max-w-2xl transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all'>\n                  <Dialog.Title as='h3' className='border-b border-gray-300 pb-4 text-center text-lg font-semibold'>\n                    Disclaimer\n                  </Dialog.Title>\n                  <div className='flex flex-col pb-6'>\n                    <p className='mt-2 text-xl font-semibold italic 2xl:mt-4'>\n                      This tool utilizes OpenAI&#39;s and Amazon Bedrock&#39;s language models to provide information\n                      and assistance. While we strive to ensure the accuracy and reliability of the information\n                      provided, the responses generated by the model may not always be accurate, complete, or\n                      up-to-date.\n                    </p>\n                    <ul className='font-montserrat'>\n                      <li className='py-2'>\n                        <span className='font-semibold'>Information Accuracy:</span> The information and responses\n                        provided by the tool are generated based on the data and patterns recognized by OpenAI&#39;s and\n                        Amazon Bedrock&#39;s language models. Users should independently verify any critical information\n                        before relying on it.\n                      </li>\n                      <li className='py-2'>\n                        <span className='font-semibold'>User Responsibility:</span> Users are responsible for how they\n                        interpret and use the information provided by the tool. The developers and operators of this\n                        tool are not liable for any damages or losses resulting from the use or misuse of the tool.\n                      </li>\n                      <li className='py-2'>\n                        <span className='font-semibold'>Company Policies:</span> If your company has policies or\n                        guidelines related to the use of OpenAI or AWS Bedrock or AI-generated content, please ensure\n                        that you adhere to those policies when using this tool. It is your responsibility to comply with\n                        your company&#39;s regulations and standards.\n                      </li>\n                      <li className='py-2'>\n                        <span className='font-semibold'>Privacy and Data Use:</span> This tool itself DOES NOT collect\n                        any data input by users. Please review the respective LLM&#39;s privacy policy for more\n                        information on how they handle your data. By using this tool, you acknowledge that you\n                        understand and agree to this disclaimer and our terms of service.\n                      </li>\n                    </ul>\n                  </div>\n                  <div className='flex items-center gap-2 border-t border-gray-300 pt-6'>\n                    <Button\n                      btnType={BUTTON_TYPES.primary}\n                      intentType={BUTTON_INTENT_TYPES.error}\n                      isDisabled={false}\n                      onClickHandle={() => {\n                        closeFn();\n                      }}\n                      fullWidth={true}\n                    >\n                      Cancel\n                    </Button>\n                    <Button\n                      btnType={BUTTON_TYPES.primary}\n                      isDisabled={false}\n                      fullWidth={true}\n                      onClickHandle={async () => {\n                        await addGenAIUsageDisclaimer(true);\n                        closeFn();\n                        openGenerateFlowTestModal();\n                      }}\n                    >\n                      Acknowledge\n                    </Button>\n                  </div>\n                </Dialog.Panel>\n              </Transition.Child>\n            </div>\n          </div>\n        </Dialog>\n      </Transition>\n    </>\n  );\n};\n\nexport default GenAIUsageDisclaimer;\n"
  },
  {
    "path": "src/components/molecules/modals/GenerateFlowTestModal.js",
    "content": "import React, { Fragment, useState, useEffect } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition, Listbox } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES, GENAI_MODELS, OBJ_TYPES } from 'constants/Common';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { Square3Stack3DIcon } from '@heroicons/react/24/outline';\nimport { generateFlowData } from '../flow/flowtestai';\nimport { init } from '../flow';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { toast } from 'react-toastify';\nimport { isEqual } from 'lodash';\nimport useCommonStore from 'stores/CommonStore';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { promiseWithTimeout } from 'utils/common';\nimport { isEmpty } from 'lodash';\nimport TextInput from 'components/atoms/common/TextInput';\nimport Textarea from 'components/atoms/flow/Textarea';\nimport { flattenItems } from 'stores/utils';\nimport { createFlowTest } from 'service/collection';\n\nconst GenerateFlowTestModal = ({ closeFn = () => null, open = false, collectionId }) => {\n  const { ipcRenderer } = window;\n\n  const collections = useCollectionStore.getState().collections;\n\n  const setShowLoader = useCommonStore((state) => state.setShowLoader);\n  const setNodes = useCanvasStore((state) => state.setNodes);\n  const setEdges = useCanvasStore((state) => state.setEdges);\n\n  const [selectedModel, setSelectedModel] = useState(null);\n  const [textareaValue, setTextareaValue] = useState('');\n  const [openaiKey, setOpenAIKey] = useState('');\n  const [geminiKey, setGeminiKey] = useState('');\n  const [bedrockAccessKeyId, setBedrockAccessKeyId] = useState('');\n  const [bedrockSecretAccessKey, setBedrockSecretAccessKey] = useState('');\n\n  const [flowtestName, setFlowtestName] = useState('');\n  const [selectedCollection, setSelectionCollection] = useState({});\n  const [selectedFolder, setSelectedFolder] = useState('');\n\n  // Error flags\n  const [showFlowtestNameError, setShowFlowtestNameError] = useState(false);\n  const [showCollectionSelectionError, setShowCollectionSelectionError] = useState(false);\n  const [showSelectedModelError, setSelectedModelError] = useState(false);\n  const [showOpenAIKeyError, setShowOpenAIKeyError] = useState(false);\n  const [showGeminiKeyError, setShowGeminiKeyError] = useState(false);\n  const [showDescribeFlowError, setShowDescribeFlowError] = useState(false);\n  const [showBedrockAccessKeyIdError, setShowBedrockAccessKeyIdError] = useState(false);\n  const [showBedrockSecretAccessKeyError, setShowBedrockSecretAccessKeyError] = useState(false);\n  //const [showFolderSelectionError, setShowFolderSelectionError] = useState(false);\n\n  useEffect(() => {\n    setSelectionCollection(collectionId ? collections.find((c) => c.id === collectionId) : {});\n  }, [collectionId, collections]);\n\n  const resetFields = () => {\n    if (!collectionId) {\n      setFlowtestName('');\n      setSelectionCollection('');\n      setSelectedFolder('');\n    }\n    setSelectedModel(null);\n    setTextareaValue('');\n    setOpenAIKey('');\n    setGeminiKey('');\n    setShowFlowtestNameError(false);\n    setShowCollectionSelectionError(false);\n    setShowOpenAIKeyError(false);\n    setShowGeminiKeyError(false);\n    setShowDescribeFlowError(false);\n    setSelectedModelError(false);\n    setShowBedrockAccessKeyIdError(false);\n    setShowBedrockSecretAccessKeyError(false);\n  };\n\n  const containsFolder = (collection) => {\n    const items = collection.items;\n    let haveFolderItem = false;\n    items.map((item) => {\n      if (item.type === OBJ_TYPES.folder) {\n        haveFolderItem = true;\n        return;\n      }\n    });\n    return haveFolderItem;\n  };\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog\n        as='div'\n        className='relative z-10'\n        onClose={() => {\n          resetFields();\n          closeFn();\n        }}\n      >\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex min-h-full items-center justify-center p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-2xl transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all'>\n                <Dialog.Title as='h3' className='border-b border-gray-300 pb-4 text-center text-lg font-semibold'>\n                  Use our AI to generate the flow\n                </Dialog.Title>\n                <div className='flex flex-col gap-6 py-6'>\n                  {!collectionId && (\n                    <div>\n                      <TextInput\n                        placeHolder={`Name`}\n                        onChangeHandler={(event) => {\n                          const flowtestName = event.target.value;\n                          setFlowtestName(flowtestName);\n                        }}\n                        name={'flowtest-name'}\n                      />\n                      {flowtestName.trim() === '' && showFlowtestNameError ? (\n                        <div className='py-2 text-red-600'>Please provide a name for your new flowtest</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  )}\n                  {!collectionId && (\n                    <>\n                      <div>\n                        <div className='flex justify-between gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light text-cyan-900 transition hover:bg-background'>\n                          <Listbox\n                            value={selectedCollection}\n                            onChange={(value) => {\n                              setSelectionCollection(value);\n                              // setSelectionCollectionId(value.id);\n                            }}\n                          >\n                            <div className='relative flex h-full w-full'>\n                              <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                                {!isEmpty(selectedCollection) ? selectedCollection.name : 'Select Collection'}\n                                <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                              </Listbox.Button>\n                              <Transition\n                                as={Fragment}\n                                leave='transition ease-in duration-100'\n                                leaveFrom='opacity-100'\n                                leaveTo='opacity-0'\n                              >\n                                <Listbox.Options className='absolute right-0 top-10 z-10 mt-1 max-h-60 w-full overflow-auto rounded bg-white py-1 text-base shadow-lg ring-1 ring-black/5'>\n                                  {collections.map((collection, index) => {\n                                    return (\n                                      <Listbox.Option\n                                        key={index}\n                                        className={({ active }) =>\n                                          `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                            active ? 'bg-background-light text-slate-900' : ''\n                                          }`\n                                        }\n                                        value={collection}\n                                      >\n                                        {({ selected }) => (\n                                          <>\n                                            <span\n                                              className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}\n                                            >\n                                              {collection.name}\n                                            </span>\n                                            {selected ? (\n                                              <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                                <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                                              </span>\n                                            ) : null}\n                                          </>\n                                        )}\n                                      </Listbox.Option>\n                                    );\n                                  })}\n                                </Listbox.Options>\n                              </Transition>\n                            </div>\n                          </Listbox>\n                        </div>\n                        {isEmpty(selectedCollection) && showCollectionSelectionError ? (\n                          <div className='py-2 text-red-600'>\n                            Please provide a collection in which you want to create your new flowtest\n                          </div>\n                        ) : (\n                          ''\n                        )}\n                      </div>\n                      {!isEmpty(selectedCollection) && containsFolder(selectedCollection) ? (\n                        <div>\n                          <div className='justify-between gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light text-cyan-900 transition hover:bg-background'>\n                            <Listbox\n                              value={selectedFolder}\n                              onChange={(value) => {\n                                setSelectedFolder(value);\n                              }}\n                            >\n                              <div className='relative flex h-full w-full'>\n                                <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                                  {!isEmpty(selectedFolder) ? selectedFolder.name : 'Select Folder'}\n                                  <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                                </Listbox.Button>\n                                <Transition\n                                  as={Fragment}\n                                  leave='transition ease-in duration-100'\n                                  leaveFrom='opacity-100'\n                                  leaveTo='opacity-0'\n                                >\n                                  <Listbox.Options className='absolute right-0 top-10 z-10 mt-1 max-h-60 w-full overflow-auto rounded bg-white py-1 text-base shadow-lg ring-1 ring-black/5'>\n                                    {flattenItems(selectedCollection.items).map((collectionItem, index) => {\n                                      if (collectionItem.type === OBJ_TYPES.folder) {\n                                        return (\n                                          <Listbox.Option\n                                            key={index}\n                                            className={({ active }) =>\n                                              `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                                active ? 'bg-background-light text-slate-900' : ''\n                                              }`\n                                            }\n                                            value={collectionItem}\n                                          >\n                                            {({ selected }) => (\n                                              <>\n                                                <span\n                                                  className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}\n                                                >\n                                                  {ipcRenderer.relative(\n                                                    selectedCollection.pathname,\n                                                    collectionItem.pathname,\n                                                  )}\n                                                </span>\n                                                {selected ? (\n                                                  <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                                    <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                                                  </span>\n                                                ) : null}\n                                              </>\n                                            )}\n                                          </Listbox.Option>\n                                        );\n                                      }\n                                    })}\n                                  </Listbox.Options>\n                                </Transition>\n                              </div>\n                            </Listbox>\n                          </div>\n                          {/* {showFolderSelectionError ? (\n                        <div className='py-2 text-red-600'>\n                          Please provide a folder in which you want to create your new flowtest\n                        </div>\n                      ) : (\n                        ''\n                      )} */}\n                        </div>\n                      ) : (\n                        ''\n                      )}\n                    </>\n                  )}\n                </div>\n                <div>\n                  {!isEmpty(selectedCollection) && (\n                    <div>\n                      <div className='justify-between gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light text-cyan-900 transition hover:bg-background'>\n                        <Listbox\n                          value={selectedModel}\n                          onChange={(m) => {\n                            if (selectedCollection && selectedCollection.dotEnvVariables) {\n                              if (GENAI_MODELS.openai) {\n                                if (\n                                  Object.prototype.hasOwnProperty.call(\n                                    selectedCollection.dotEnvVariables,\n                                    'OPENAI_APIKEY',\n                                  )\n                                ) {\n                                  setOpenAIKey(selectedCollection.dotEnvVariables['OPENAI_APIKEY']);\n                                }\n                              }\n\n                              if (GENAI_MODELS.gemini) {\n                                if (\n                                  Object.prototype.hasOwnProperty.call(\n                                    selectedCollection.dotEnvVariables,\n                                    'GEMINI_APIKEY',\n                                  )\n                                ) {\n                                  setGeminiKey(selectedCollection.dotEnvVariables['GEMINI_APIKEY']);\n                                }\n                              }\n\n                              if (GENAI_MODELS.bedrock_claude) {\n                                if (\n                                  Object.prototype.hasOwnProperty.call(\n                                    selectedCollection.dotEnvVariables,\n                                    'BEDROCK_ACCESS_KEYID',\n                                  )\n                                ) {\n                                  setBedrockAccessKeyId(selectedCollection.dotEnvVariables['BEDROCK_ACCESS_KEYID']);\n                                }\n\n                                if (\n                                  Object.prototype.hasOwnProperty.call(\n                                    selectedCollection.dotEnvVariables,\n                                    'BEDROCK_SECRET_ACCESSKEY',\n                                  )\n                                ) {\n                                  setBedrockSecretAccessKey(\n                                    selectedCollection.dotEnvVariables['BEDROCK_SECRET_ACCESSKEY'],\n                                  );\n                                }\n                              }\n                            }\n                            setSelectedModel(m);\n                          }}\n                        >\n                          <div className='relative flex h-full w-full'>\n                            <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                              {selectedModel ? selectedModel : 'Select model'}\n                              <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                            </Listbox.Button>\n                            <Transition\n                              as={Fragment}\n                              leave='transition ease-in duration-100'\n                              leaveFrom='opacity-100'\n                              leaveTo='opacity-0'\n                            >\n                              <Listbox.Options className='absolute right-0 top-10 z-10 mt-1 max-h-60 w-full overflow-auto rounded bg-white py-1 text-base shadow-lg ring-1 ring-black/5'>\n                                {Object.values(GENAI_MODELS).map((modelType) => (\n                                  <Listbox.Option\n                                    key={modelType}\n                                    className={({ active }) =>\n                                      `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                        active ? 'bg-background-light text-slate-900' : ''\n                                      }`\n                                    }\n                                    value={modelType}\n                                  >\n                                    {({ selected }) => (\n                                      <>\n                                        <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>\n                                          {modelType}\n                                        </span>\n                                        {selected ? (\n                                          <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                            <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                                          </span>\n                                        ) : null}\n                                      </>\n                                    )}\n                                  </Listbox.Option>\n                                ))}\n                              </Listbox.Options>\n                            </Transition>\n                          </div>\n                        </Listbox>\n                      </div>\n                      {selectedModel === null && showSelectedModelError ? (\n                        <div className='py-2 text-red-600'> Please select a model</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  )}\n                  {selectedModel === GENAI_MODELS.gemini ? (\n                    <div>\n                      <div className='mt-6 flex h-12 w-full items-center justify-center rounded border border-cyan-900 bg-background-light text-sm text-cyan-900 hover:bg-background'>\n                        <label\n                          className='flex h-full w-32 items-center border-r border-cyan-900 bg-transparent px-4'\n                          htmlFor='openAIkey'\n                        >\n                          API_KEY\n                        </label>\n                        <input\n                          id='geminikey'\n                          type='text'\n                          className='nodrag nowheel block w-full bg-transparent p-2.5 outline-none'\n                          name='keyName'\n                          placeholder='Enter your GEMINI api key'\n                          value={geminiKey.trim()}\n                          //readOnly='readonly'\n                          onChange={(e) => setGeminiKey(e.target.value)}\n                        />\n                      </div>\n                      {geminiKey.trim() === '' && showGeminiKeyError ? (\n                        <div className='py-2 text-red-600'> {`Please enter ${selectedModel} api key`}</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  ) : (\n                    ''\n                  )}\n                  {selectedModel === GENAI_MODELS.openai ? (\n                    <div>\n                      <div className='mt-6 flex h-12 w-full items-center justify-center rounded border border-cyan-900 bg-background-light text-sm text-cyan-900 hover:bg-background'>\n                        <label\n                          className='flex h-full w-32 items-center border-r border-cyan-900 bg-transparent px-4'\n                          htmlFor='openAIkey'\n                        >\n                          API_KEY\n                        </label>\n                        <input\n                          id='openAIkey'\n                          type='text'\n                          className='nodrag nowheel block w-full bg-transparent p-2.5 outline-none'\n                          name='keyName'\n                          placeholder='Enter your OPENAI api key'\n                          value={openaiKey.trim()}\n                          //readOnly='readonly'\n                          onChange={(e) => setOpenAIKey(e.target.value)}\n                        />\n                      </div>\n                      {openaiKey.trim() === '' && showOpenAIKeyError ? (\n                        <div className='py-2 text-red-600'> {`Please enter ${selectedModel} api key`}</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  ) : (\n                    ''\n                  )}\n                  {selectedModel === GENAI_MODELS.bedrock_claude ? (\n                    <div>\n                      <div className='mt-6 flex h-12 w-full items-center justify-center rounded border border-cyan-900 bg-background-light text-sm text-cyan-900 hover:bg-background'>\n                        <label\n                          className='flex h-full w-32 items-center border-r border-cyan-900 bg-transparent px-4'\n                          htmlFor='bedrockAccessKeyId'\n                        >\n                          ACCESS_KEYID\n                        </label>\n                        <input\n                          id='bedrockAccessKeyId'\n                          type='text'\n                          className='nodrag nowheel block w-full bg-transparent p-2.5 outline-none'\n                          name='keyId'\n                          placeholder='Enter your BEDROCK access key id'\n                          value={bedrockAccessKeyId.trim()}\n                          //readOnly='readonly'\n                          onChange={(e) => setBedrockAccessKeyId(e.target.value)}\n                        />\n                      </div>\n                      {bedrockAccessKeyId.trim() === '' && showBedrockAccessKeyIdError ? (\n                        <div className='py-2 text-red-600'> {`Please enter ${selectedModel} access key id`}</div>\n                      ) : (\n                        ''\n                      )}\n                      <div className='mt-6 flex h-12 w-full items-center justify-center rounded border border-cyan-900 bg-background-light text-sm text-cyan-900 hover:bg-background'>\n                        <label\n                          className='flex h-full w-fit items-center border-r border-cyan-900 bg-transparent px-4'\n                          htmlFor='bedrockSecretAccessKey'\n                        >\n                          SECRET_ACCESSKEY\n                        </label>\n                        <input\n                          id='bedrockSecretAccessKey'\n                          type='text'\n                          className='nodrag nowheel block w-full bg-transparent p-2.5 outline-none'\n                          name='keyName'\n                          placeholder='Enter your BEDROCK secret access key'\n                          value={bedrockSecretAccessKey.trim()}\n                          //readOnly='readonly'\n                          onChange={(e) => setBedrockSecretAccessKey(e.target.value)}\n                        />\n                      </div>\n                      {bedrockSecretAccessKey.trim() === '' && showBedrockSecretAccessKeyError ? (\n                        <div className='py-2 text-red-600'> {`Please enter ${selectedModel} secret access key`}</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  ) : (\n                    ''\n                  )}\n                  <div>\n                    <div className='mt-6'>\n                      <Textarea\n                        id='gen-ai-text'\n                        placeHolder={'Describe your flow step by step. Be as descriptive as possible.'}\n                        onChangeHandler={(event) => setTextareaValue(event.target.value)}\n                        name={'gen-ai-text'}\n                        value={textareaValue}\n                        rows={12}\n                      />\n                    </div>\n                    {textareaValue.trim() === '' && showDescribeFlowError ? (\n                      <div className='py-2 text-red-600'> Please describe your flow</div>\n                    ) : (\n                      ''\n                    )}\n                  </div>\n                </div>\n                <div className='mt-6 flex items-center gap-2'>\n                  <Button\n                    btnType={BUTTON_TYPES.primary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    onClickHandle={() => {\n                      resetFields();\n                      closeFn();\n                    }}\n                    fullWidth={true}\n                  >\n                    Cancel\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.primary}\n                    isDisabled={false}\n                    fullWidth={true}\n                    onClickHandle={() => {\n                      let pathName = '';\n                      if (!collectionId) {\n                        if (!flowtestName || flowtestName.trim() === '') {\n                          setShowFlowtestNameError(true);\n                          return;\n                        }\n                        if (!selectedCollection || !selectedCollection.id || selectedCollection.id === '') {\n                          setShowCollectionSelectionError(true);\n                          return;\n                        }\n\n                        if (!selectedFolder || !selectedFolder.pathname || selectedFolder.pathname === '') {\n                          pathName = selectedCollection.pathname;\n                        } else {\n                          pathName = selectedFolder.pathname;\n                        }\n                      }\n\n                      if (selectedModel === null) {\n                        setSelectedModelError(true);\n                        return;\n                      }\n\n                      if (selectedModel === GENAI_MODELS.openai && (!openaiKey || openaiKey.trim() === '')) {\n                        setShowOpenAIKeyError(true);\n                        return;\n                      }\n\n                      if (selectedModel === GENAI_MODELS.gemini && (!geminiKey || geminiKey.trim() === '')) {\n                        setShowGeminiKeyError(true);\n                        return;\n                      }\n\n                      if (\n                        selectedModel === GENAI_MODELS.bedrock_claude &&\n                        (!bedrockAccessKeyId || bedrockAccessKeyId.trim() === '')\n                      ) {\n                        setShowBedrockAccessKeyIdError(true);\n                        return;\n                      }\n\n                      if (\n                        selectedModel === GENAI_MODELS.bedrock_claude &&\n                        (!bedrockSecretAccessKey || bedrockSecretAccessKey.trim() === '')\n                      ) {\n                        setShowBedrockSecretAccessKeyError(true);\n                        return;\n                      }\n\n                      if (!textareaValue || textareaValue.trim() === '') {\n                        setShowDescribeFlowError(true);\n                        return;\n                      }\n\n                      setShowFlowtestNameError(false);\n                      setShowCollectionSelectionError(false);\n                      setShowOpenAIKeyError(false);\n                      setShowGeminiKeyError(false);\n                      setShowBedrockAccessKeyIdError(false);\n                      setShowBedrockSecretAccessKeyError(false);\n                      setShowDescribeFlowError(false);\n                      setSelectedModelError(false);\n\n                      const creds =\n                        selectedModel === GENAI_MODELS.openai\n                          ? openaiKey\n                          : selectedModel === GENAI_MODELS.gemini\n                            ? geminiKey\n                            : { accessKeyId: bedrockAccessKeyId, secretAccessKey: bedrockSecretAccessKey };\n                      function gen() {\n                        setShowLoader(true);\n                        promiseWithTimeout(\n                          generateFlowData(textareaValue, selectedModel, creds, selectedCollection.id),\n                          60000,\n                        )\n                          .then((flowData) => {\n                            setShowLoader(false);\n                            if (isEqual(flowData.nodes, [])) {\n                              toast.info(`${selectedModel} was not able to evaluate the instructions properly`);\n                            } else {\n                              const result = init(flowData);\n                              setNodes(result.nodes);\n                              setEdges(result.edges);\n                            }\n                            closeFn();\n                            resetFields();\n                          })\n                          .catch((error) => {\n                            setShowLoader(false);\n                            toast.error(`Error while generating flow data ${error}`);\n                            closeFn();\n                            resetFields();\n                          });\n                      }\n\n                      if (collectionId) {\n                        gen();\n                      } else {\n                        createFlowTest(flowtestName, pathName, selectedCollection.id)\n                          .then((result) => {\n                            toast.success(`Created a new flowtest: ${flowtestName}`);\n                            gen();\n                          })\n                          .catch((error) => {\n                            console.log(`Error creating new flowtest: ${error}`);\n                            toast.error(`Error creating new flowtest`);\n                            closeFn();\n                            resetFields();\n                          });\n                      }\n                    }}\n                  >\n                    Generate\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nGenerateFlowTestModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\nexport default GenerateFlowTestModal;\n"
  },
  {
    "path": "src/components/molecules/modals/ImportCollectionModal.js",
    "content": "import React, { Fragment, useRef, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport { CheckIcon } from '@heroicons/react/24/outline';\nimport ImportCollectionTypes from 'constants/ImportCollectionTypes';\nimport { createCollection } from 'service/collection';\nimport { toast } from 'react-toastify';\n\nconst ImportCollectionModal = ({ closeFn = () => null, open = false }) => {\n  const [selectedFilePath, setSelectedFilePath] = useState('');\n  const importYamlFile = useRef(null);\n\n  const handleImportCollectionClick = (event) => {\n    const elem = event.currentTarget;\n    const importType = elem.dataset.importType;\n    importCollectionByType(importType);\n  };\n\n  const importCollectionByType = (importTypeVal) => {\n    switch (importTypeVal) {\n      case ImportCollectionTypes.YAML:\n        importCollectionByYaml();\n        break;\n    }\n  };\n\n  const importCollectionByYaml = () => {\n    importYamlFile.current.click();\n  };\n\n  const selectDirectory = () => {\n    const { ipcRenderer } = window;\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer.invoke('renderer:open-directory-selection-dialog').then(resolve).catch(reject);\n    });\n  };\n\n  const handleFileSelection = async (event) => {\n    const yamlPath = event.target.files[0].path;\n    setSelectedFilePath(yamlPath);\n  };\n\n  const handleDirectorySelectionClick = async () => {\n    // This solution will only work in Electron not in webapp\n    selectDirectory()\n      .then((dirPath) => {\n        // if user presses cancel in choosing directory dialog, this is returned undefined\n        if (dirPath) {\n          createCollection(selectedFilePath, dirPath);\n          toast.success('Successfully created the collection');\n        }\n      })\n      .catch((error) => {\n        console.log(`Failed to create collection: ${error}`);\n        toast.error('Failed to create the collection');\n      });\n\n    //resetting data\n    setSelectedFilePath('');\n    closeFn();\n    return;\n  };\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog\n        as='div'\n        className='relative z-10'\n        onClose={() => {\n          setSelectedFilePath('');\n          closeFn();\n        }}\n      >\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-xl p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Create a Collection\n                </Dialog.Title>\n                {/* ToDo: Add the message of instructions here, if that is not required then we can remove this div */}\n                {/* <div className='mt-4'>\n                  <p> Message or instructions here</p>\n                </div> */}\n                <div className='mt-4'>\n                  <ul className='text-lg font-medium'>\n                    <li\n                      className={`cursor-pointer rounded border border-transparent px-2 py-4 hover:bg-background-light ${selectedFilePath ? 'text-green-500' : ''}`}\n                      onClick={handleImportCollectionClick}\n                      data-import-type='yaml'\n                    >\n                      <div className='flex items-center justify-start gap-4'>\n                        <div\n                          className={`${selectedFilePath ? 'border-green-600 bg-green-500 text-white before:h-40' : 'border-cyan-900 before:h-[38px]'} relative flex h-8 w-8 items-center justify-center rounded-full border-4 before:absolute before:left-[10px] before:top-[27px] before:w-[4px] before:bg-cyan-900 before:opacity-100`}\n                        >\n                          {selectedFilePath ? <CheckIcon className='w-5 h-5' /> : '1'}\n                        </div>\n                        <div className='flex items-center justify-start'>\n                          {/* <DocumentArrowUpIcon className='w-5 h-5' /> */}\n                          Import an OpenAPI V3 spec\n                          {/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}\n                          <div className='hidden'>\n                            <input\n                              type='file'\n                              id='file'\n                              accept='.yaml,.yml,.json'\n                              ref={importYamlFile}\n                              onChange={handleFileSelection}\n                            />\n                          </div>\n                        </div>\n                      </div>\n                      {selectedFilePath ? (\n                        <div className='px-2 py-4 my-4 ml-12 text-green-600 bg-green-100 border border-green-600 rounded'>\n                          {selectedFilePath}\n                        </div>\n                      ) : (\n                        ''\n                      )}\n                    </li>\n                    <li\n                      className={`flex cursor-pointer items-center justify-start gap-4 px-2 py-4 hover:bg-background-light ${selectedFilePath ? 'cursor-default' : 'cursor-not-allowed'}`}\n                      onClick={handleDirectorySelectionClick}\n                    >\n                      <div className='flex items-center justify-center w-8 h-8 border-4 rounded-full border-cyan-900'>\n                        2\n                      </div>\n                      <div\n                        className={`flex items-center justify-start ${selectedFilePath ? 'cursor-default' : 'cursor-not-allowed text-gray-400'}`}\n                      >\n                        {/* <FolderPlusIcon className='w-5 h-5' /> */}\n                        Select a directory to create your collection\n                      </div>\n                    </li>\n                  </ul>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nImportCollectionModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\n\nexport default ImportCollectionModal;\n"
  },
  {
    "path": "src/components/molecules/modals/OpenCollectionModal.js",
    "content": "import React, { Fragment, useRef, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport { DocumentArrowUpIcon, CheckIcon } from '@heroicons/react/24/outline';\nimport ImportCollectionTypes from 'constants/ImportCollectionTypes';\nimport { openCollection } from 'service/collection';\nimport { toast } from 'react-toastify';\n\nconst OpenCollectionModal = ({ closeFn = () => null, open = false }) => {\n  const [selectedFilePath, setSelectedFilePath] = useState('');\n  const importYamlFile = useRef(null);\n  const handleImportCollectionClick = (event) => {\n    const elem = event.currentTarget;\n    const importType = elem.dataset.importType;\n    importCollectionByType(importType);\n  };\n\n  const importCollectionByType = (importTypeVal) => {\n    switch (importTypeVal) {\n      case ImportCollectionTypes.YAML:\n        importCollectionByYaml();\n        break;\n    }\n  };\n\n  const importCollectionByYaml = () => {\n    importYamlFile.current.click();\n  };\n\n  const selectDirectory = () => {\n    const { ipcRenderer } = window;\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer.invoke('renderer:open-directory-selection-dialog').then(resolve).catch(reject);\n    });\n  };\n\n  const handleFileSelection = async (event) => {\n    const yamlPath = event.target.files[0].path;\n    setSelectedFilePath(yamlPath);\n  };\n\n  const handleDirectorySelectionClick = async () => {\n    // This solution will only work in Electron not in webapp\n    selectDirectory()\n      .then((dirPath) => {\n        // if user presses cancel in choosing directory dialog, this is returned undefined\n        if (dirPath) {\n          openCollection(selectedFilePath, dirPath).catch((error) => {\n            console.log(`Failed to open collection: ${error}`);\n            toast.error('Failed to create the collection');\n          });\n        }\n      })\n      .catch((error) => {\n        console.log(`Failed to open collection: ${error}`);\n        toast.error('Failed to open the collection');\n      });\n\n    //resetting data\n    setSelectedFilePath('');\n    closeFn();\n  };\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-xl p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Open a Collection\n                </Dialog.Title>\n                {/* ToDo: Add the message of instructions here, if that is not required then we can remove this div */}\n                {/* <div className='mt-4'>\n                  <p> Message or instructions here</p>\n                </div> */}\n                <div className='mt-4'>\n                  <ul className='text-lg font-medium'>\n                    <li\n                      className={`cursor-pointer rounded border border-transparent px-2 py-4 hover:bg-background-light ${selectedFilePath ? 'text-green-500' : ''}`}\n                      onClick={handleImportCollectionClick}\n                      data-import-type='yaml'\n                    >\n                      <div className='flex items-center justify-start gap-4'>\n                        <div\n                          className={`${selectedFilePath ? 'border-green-600 bg-green-500 text-white before:h-40' : 'border-cyan-900 before:h-[38px]'} relative flex h-8 w-8 items-center justify-center rounded-full border-4 before:absolute before:left-[10px] before:top-[27px] before:w-[4px] before:bg-cyan-900 before:opacity-100`}\n                        >\n                          {selectedFilePath ? <CheckIcon className='w-5 h-5' /> : '1'}\n                        </div>\n                        <div className='flex items-center justify-start'>\n                          {/* <DocumentArrowUpIcon className='w-4 h-4' /> */}\n                          Import an OpenAPI V3 spec\n                          {/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}\n                          <div className='hidden'>\n                            <input\n                              type='file'\n                              id='file'\n                              accept='.yaml,.yml,.json'\n                              ref={importYamlFile}\n                              onChange={handleFileSelection}\n                            />\n                          </div>\n                        </div>\n                      </div>\n\n                      {selectedFilePath ? (\n                        <div className='px-2 py-4 my-4 ml-12 text-green-600 bg-green-100 border border-green-600 rounded'>\n                          {selectedFilePath}\n                        </div>\n                      ) : (\n                        ''\n                      )}\n                    </li>\n                    <li\n                      className={`flex cursor-pointer items-center justify-start gap-4 px-2 py-4 hover:bg-background-light ${selectedFilePath ? 'cursor-default' : 'cursor-not-allowed'}`}\n                      onClick={handleDirectorySelectionClick}\n                    >\n                      <div className='flex items-center justify-center w-8 h-8 border-4 rounded-full border-cyan-900'>\n                        2\n                      </div>\n                      <div\n                        className={`flex items-center justify-start ${selectedFilePath ? 'cursor-default' : 'cursor-not-allowed text-gray-400'}`}\n                      >\n                        {/* <FolderPlusIcon className='w-5 h-5' /> */}\n                        Select the directory of an existing collection\n                      </div>\n                    </li>\n                  </ul>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nOpenCollectionModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\n\nexport default OpenCollectionModal;\n"
  },
  {
    "path": "src/components/molecules/modals/OutputNodeExpandedModal.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition, Listbox } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES, GENAI_MODELS } from 'constants/Common';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { Square3Stack3DIcon } from '@heroicons/react/24/outline';\nimport { generateFlowData } from '../flow/flowtestai';\nimport { init } from '../flow';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { toast } from 'react-toastify';\nimport { isEqual } from 'lodash';\nimport useCommonStore from 'stores/CommonStore';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { promiseWithTimeout } from 'utils/common';\nimport { Editor } from 'components/atoms/Editor';\n\nconst OuputNodeExpandedModal = ({ closeFn = () => null, open = false, data }) => {\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-[672px] transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all '>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Ouput\n                </Dialog.Title>\n                <div className='mt-6'>\n                  <Editor\n                    name='output-text'\n                    value={JSON.stringify(data, null, 2)}\n                    readOnly={true}\n                    classes={'w-full max-h-[672px]'}\n                  />\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nOuputNodeExpandedModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\nexport default OuputNodeExpandedModal;\n"
  },
  {
    "path": "src/components/molecules/modals/SaveFlowModal.js",
    "content": "import React from 'react';\nimport { PropTypes } from 'prop-types';\nimport { InboxArrowDownIcon } from '@heroicons/react/24/outline';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport { updateEnvironmentFile, updateFlowTest } from 'service/collection';\nimport { toast } from 'react-toastify';\nimport { BUTTON_TYPES, OBJ_TYPES } from 'constants/Common';\nimport Button from 'components/atoms/common/Button';\nimport { isSaveNeeded } from 'components/atoms/util';\n\nexport const saveHandle = (tab) => {\n  if (tab.type == OBJ_TYPES.flowtest && tab.flowDataDraft) {\n    if (isSaveNeeded(tab.flowData, tab.flowDataDraft)) {\n      updateFlowTest(tab.pathname, tab.flowDataDraft, tab.collectionId)\n        .then((result) => {\n          console.log(\n            `Updated flowtest: path = ${tab.pathname}, collectionId = ${tab.collectionId}, result: ${result}`,\n          );\n          toast.success(`Updated the flowtest: ${tab.pathname}`);\n        })\n        .catch((error) => {\n          console.log(`Error updating flowtest = ${tab.pathname}: ${error}`);\n          toast.error(`Error while updating flowtest: ${tab.pathname}`);\n        });\n    } else {\n      toast.info('Nothing to save');\n    }\n  } else if (tab.type == OBJ_TYPES.environment && tab.variablesDraft) {\n    updateEnvironmentFile(tab.name, tab.collectionId, tab.variablesDraft)\n      .then((result) => {\n        console.log(`Updated environment: name = ${tab.name}, collectionId = ${tab.collectionId}, result: ${result}`);\n        toast.success(`Updated environment: ${tab.name}`);\n      })\n      .catch((error) => {\n        console.log(`Error updating environment = ${tab.name}: ${error}`);\n        toast.error(`Error while updating environment: ${tab.name}`);\n      });\n  }\n};\n\nconst SaveFlowModal = ({ tab }) => {\n  return (\n    <Button\n      btnType={BUTTON_TYPES.secondary}\n      isDisabled={false}\n      onClickHandle={() => saveHandle(tab)}\n      fullWidth={true}\n      onlyIcon={true}\n      padding={'px-4 py-2.5'}\n    >\n      <Tippy content='Save' placement='top'>\n        <InboxArrowDownIcon className='w-5 h-5' />\n      </Tippy>\n    </Button>\n  );\n};\n\nSaveFlowModal.propTypes = {\n  tab: PropTypes.object.isRequired,\n};\n\nexport default SaveFlowModal;\n"
  },
  {
    "path": "src/components/molecules/modals/SettingsModal.js",
    "content": "import React, { Fragment, useState, useEffect } from 'react';\nimport { Dialog, Transition, Tab } from '@headlessui/react';\n// import { useForm } from 'react-hook-form';\n// import { z } from 'zod';\n// import { zodResolver } from '@hookform/resolvers/zod';\nimport { XCircleIcon } from '@heroicons/react/20/solid';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport { addLogSyncConfig } from 'service/settings';\nimport useSettingsStore from 'stores/SettingsStore';\n\n// const schema = z.object({\n//   enabled: z.boolean(),\n//   accessId: z.string(),\n//   accessKey: z.string(),\n// });\n\nconst SettingsModal = ({ closeFn = () => null, open = false, initialTab = 0 }) => {\n  const [successFullSubmissionMessage, showSuccessFullSubmissionMessage] = useState(false);\n  const [failureFullSubmissionMessage, showFailureFullSubmissionMessage] = useState(false);\n  const config = useSettingsStore((state) => state.logSyncConfig);\n  const [enabled, setEnabled] = useState(false);\n  const [accessId, setAccessId] = useState('');\n  const [accessKey, setAccessKey] = useState('');\n\n  // const {\n  //   register,\n  //   handleSubmit,\n  //   setValue,\n  //   setError,\n  //   formState: { errors, isSubmitting },\n  // } = useForm({\n  //   defaultValues: {\n  //     enabled: false,\n  //     accessId: '',\n  //     accessKey: '',\n  //   },\n  //   resolver: zodResolver(schema),\n  // });\n\n  useEffect(() => {\n    setEnabled(config?.enabled || false);\n    setAccessId(config?.accessId || '');\n    setAccessKey(config?.accessKey || '');\n  }, [config]);\n\n  const onFormSubmit = async () => {\n    try {\n      await addLogSyncConfig(enabled, 'https://www.useflowtest.ai', accessId, accessKey);\n      // send the form data as a request\n      showSuccessFullSubmissionMessage(true);\n      closeFn();\n    } catch (error) {\n      // To show error message from the request handler\n      showFailureFullSubmissionMessage(true);\n    }\n  };\n\n  return (\n    <>\n      <Transition appear show={open} as={Fragment}>\n        <Dialog\n          as='div'\n          className='relative z-10'\n          onClose={() => {\n            setEnabled(config?.enabled || false);\n            setAccessId(config?.accessId || '');\n            setAccessKey(config?.accessKey || '');\n            closeFn();\n          }}\n        >\n          <Transition.Child\n            as={Fragment}\n            enter='ease-out duration-300'\n            enterFrom='opacity-0'\n            enterTo='opacity-100'\n            leave='ease-in duration-200'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            <div className='fixed inset-0 bg-black/25' />\n          </Transition.Child>\n\n          <div className='fixed inset-0 overflow-y-auto'>\n            <div className='flex min-h-full items-center justify-center p-4 text-center'>\n              <Transition.Child\n                as={Fragment}\n                enter='ease-out duration-300'\n                enterFrom='opacity-0 scale-95'\n                enterTo='opacity-100 scale-100'\n                leave='ease-in duration-200'\n                leaveFrom='opacity-100 scale-100'\n                leaveTo='opacity-0 scale-95'\n              >\n                <Dialog.Panel className='h-[40rem] w-full max-w-5xl transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all'>\n                  <Dialog.Title as='div' className='flex items-center justify-between border-b border-gray-300 pb-4'>\n                    <h1 className='text-3xl font-semibold'>Settings</h1>\n                    <button onClick={closeFn} className='text-gray-400 hover:text-gray-600'>\n                      <XCircleIcon className='h-6 w-6' />\n                    </button>\n                  </Dialog.Title>\n                  <div className='py-4'>\n                    <Tab.Group defaultIndex={initialTab}>\n                      <Tab.List className='flex'>\n                        <Tab\n                          className={({ selected }) =>\n                            `w-full p-2 ${selected ? 'rounded border border-cyan-900 bg-slate-100' : ''}`\n                          }\n                        >\n                          Scans\n                        </Tab>\n                        <Tab\n                          className={({ selected }) =>\n                            `w-full p-2 ${selected ? 'rounded border border-cyan-900 bg-slate-100' : ''}`\n                          }\n                        >\n                          Theme\n                        </Tab>\n                      </Tab.List>\n                      <Tab.Panels className='mt-2'>\n                        <Tab.Panel className='py-4'>\n                          {/* Scans Content */}\n                          <div className='bg-card min-h-[20vh] w-full rounded-lg border border-gray-300 p-4 shadow-sm'>\n                            <div className='py-2'>\n                              <p className='text-lg'>\n                                Scans aim to provide anayltics and observability for your flows. <br />\n                                <a\n                                  href='https://www.useflowtest.ai/'\n                                  target='_blank'\n                                  rel='noreferrer'\n                                  className='text-blue-500 hover:underline'\n                                >\n                                  Get Access Keys\n                                </a>\n                              </p>\n                            </div>\n                            <div className='py-2'>\n                              <label htmlFor='enabled' className='block text-lg font-medium text-gray-700'>\n                                Enabled\n                              </label>\n                              <input\n                                type='checkbox'\n                                checked={enabled}\n                                onChange={() => setEnabled(!enabled)}\n                                id='enabled'\n                                className='block'\n                              />\n                            </div>\n                            <div className='py-2'>\n                              <label htmlFor='accessId' className='block text-lg font-medium text-gray-700'>\n                                Access Id\n                              </label>\n                              <input\n                                value={accessId}\n                                onChange={(e) => setAccessId(e.target.value)}\n                                type='text'\n                                placeholder='Access Id'\n                                className='mb-2 block w-full rounded border border-slate-700 bg-background-light p-2.5 text-sm text-slate-900 outline-none'\n                              />\n                            </div>\n                            <div className='py-2'>\n                              <label htmlFor='accessKey' className='block text-lg font-medium text-gray-700'>\n                                Access Key\n                              </label>\n                              <input\n                                value={accessKey}\n                                onChange={(e) => setAccessKey(e.target.value)}\n                                type='text'\n                                placeholder='Access Key'\n                                className='mb-2 block w-full rounded border border-slate-700 bg-background-light p-2.5 text-sm text-slate-900 outline-none'\n                              />\n                            </div>\n                            <div>\n                              {failureFullSubmissionMessage && (\n                                <div className='text-red-500'> Failed to saved settings</div>\n                              )}\n                              {successFullSubmissionMessage && (\n                                <div className='text-green-500'> Successfully saved settings</div>\n                              )}\n                            </div>\n                          </div>\n                          <div className='mt-6 flex w-full justify-center'>\n                            <Button\n                              btnType={BUTTON_TYPES.primary}\n                              fullWidth={true}\n                              onClickHandle={async () => await onFormSubmit()}\n                            >\n                              {'Save'}\n                            </Button>\n                          </div>\n                        </Tab.Panel>\n                        <Tab.Panel className='p-4'>\n                          {/* Theme Content */}\n                          <form className='flex flex-col items-start gap-4'>\n                            <div className='bg-card min-h-[20vh] w-full rounded-lg border border-gray-300 p-4 shadow-sm'>\n                              <div className='flex items-center'>\n                                <input type='radio' id='light' name='theme' value='light' defaultChecked />\n                                <label htmlFor='light' className='ml-2 text-lg'>\n                                  Light\n                                </label>\n                              </div>\n                              <div className='flex items-center'>\n                                <input type='radio' id='dark' name='theme' value='dark' />\n                                <label htmlFor='dark' className='ml-2 text-lg'>\n                                  Dark\n                                </label>\n                              </div>\n                              <div className='flex items-center'>\n                                <input type='radio' id='system' name='theme' value='system' />\n                                <label htmlFor='system' className='ml-2 text-lg'>\n                                  System\n                                </label>\n                              </div>\n                            </div>\n                          </form>\n                        </Tab.Panel>\n                      </Tab.Panels>\n                    </Tab.Group>\n                  </div>\n                </Dialog.Panel>\n              </Transition.Child>\n            </div>\n          </div>\n        </Dialog>\n      </Transition>\n    </>\n  );\n};\n\nexport default SettingsModal;\n"
  },
  {
    "path": "src/components/molecules/modals/create/NewCollectionModal.js",
    "content": "import React, { Fragment } from 'react';\nimport { Dialog, Transition } from '@headlessui/react';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport { PropTypes } from 'prop-types';\nimport TextInput from 'components/atoms/common/TextInput';\n\nconst NewCollectionModal = ({ closeFn = () => null, open = false }) => {\n  // ToDo: Save the file with the given file name\n  // function handleSave() {\n  //   setIsNewCollectionModalOpen(false);\n  // }\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Create a new collection\n                </Dialog.Title>\n                <div className='mt-6'>\n                  <TextInput placeHolder={`Collection name`} name={'Collection name'} />\n                </div>\n                <div className='flex items-center gap-2 mt-6'>\n                  <Button\n                    btnType={BUTTON_TYPES.primary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    onClickHandle={closeFn}\n                    fullWidth={true}\n                  >\n                    Cancel\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.primary}\n                    intentType={BUTTON_INTENT_TYPES.success}\n                    isDisabled={false}\n                    fullWidth={true}\n                  >\n                    Create\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nNewCollectionModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n};\n\nexport default NewCollectionModal;\n"
  },
  {
    "path": "src/components/molecules/modals/flow/AddVariableModal.js",
    "content": "import React, { useState, Fragment } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport 'tippy.js/dist/tippy.css';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport TextInputWithLabel from 'components/atoms/common/TextInputWithLabel';\n\nexport const variableTypes = [\n  {\n    value: 'String',\n    label: 'String',\n  },\n  {\n    value: 'Select',\n    label: 'Select',\n  },\n  {\n    value: 'Number',\n    label: 'Number',\n  },\n  {\n    value: 'Boolean',\n    label: 'Boolean',\n  },\n  {\n    value: 'Now',\n    label: 'Now',\n  },\n];\n\nconst AddVariableModal = ({ closeFn = () => null, open = false, modalType, onVariableAdd }) => {\n  const [variableName, setVariableName] = useState('');\n  const [variableType, setVariableType] = useState('String');\n\n  return (\n    <Transition appear show={open} as={Fragment}>\n      <Dialog as='div' className='relative z-10' onClose={closeFn}>\n        <Transition.Child\n          as={Fragment}\n          enter='ease-out duration-300'\n          enterFrom='opacity-0'\n          enterTo='opacity-100'\n          leave='ease-in duration-200'\n          leaveFrom='opacity-100'\n          leaveTo='opacity-0'\n        >\n          <div className='fixed inset-0 bg-black/25' />\n        </Transition.Child>\n\n        <div className='fixed inset-0 overflow-y-auto'>\n          <div className='flex items-center justify-center min-h-full p-4 text-center'>\n            <Transition.Child\n              as={Fragment}\n              enter='ease-out duration-300'\n              enterFrom='opacity-0 scale-95'\n              enterTo='opacity-100 scale-100'\n              leave='ease-in duration-200'\n              leaveFrom='opacity-100 scale-100'\n              leaveTo='opacity-0 scale-95'\n            >\n              <Dialog.Panel className='w-full p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl max-w-min'>\n                <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                  Create a new variable\n                </Dialog.Title>\n\n                <div className='order bg-background-light mt-6 flex w-24 min-w-[40vw] items-center justify-center rounded border border-slate-700 p-2.5 text-sm text-slate-900 outline-none'>\n                  <input\n                    id='keyName'\n                    type='text'\n                    className='block w-full h-12 bg-transparent outline-none nodrag nowheel'\n                    name='keyName'\n                    placeholder='Enter variable name'\n                    onChange={(e) => setVariableName(e.target.value)}\n                  />\n                  <select\n                    onChange={(event) => {\n                      const selectedValue = event.target.value;\n                      setVariableType(selectedValue);\n                    }}\n                    name='var-input-type'\n                    defaultValue={variableType}\n                    className='nodrag h-12 w-full max-w-[30%] border-l border-slate-700 bg-transparent p-0 px-1 outline-none'\n                  >\n                    {variableTypes.map((option) => (\n                      <option key={option.value} value={option.value}>\n                        {option.label}\n                      </option>\n                    ))}\n                  </select>\n                </div>\n                <div className='flex items-center gap-2 mt-6'>\n                  <Button\n                    btnType={BUTTON_TYPES.secondary}\n                    intentType={BUTTON_INTENT_TYPES.error}\n                    isDisabled={false}\n                    onClickHandle={closeFn}\n                    fullWidth={true}\n                  >\n                    Cancel\n                  </Button>\n                  <Button\n                    btnType={BUTTON_TYPES.primary}\n                    isDisabled={false}\n                    onClickHandle={() => {\n                      if (variableName.trim() != '') {\n                        onVariableAdd(modalType, variableName, variableType);\n                      }\n                      closeFn();\n                    }}\n                    fullWidth={true}\n                  >\n                    Add variable\n                  </Button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nAddVariableModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n  onVariableAdd: PropTypes.func.isRequired,\n};\n\nexport default AddVariableModal;\n"
  },
  {
    "path": "src/components/molecules/modals/flow/NewFlowTestModal.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition, Listbox } from '@headlessui/react';\nimport { createFolder, createFlowTest, createEnvironmentFile } from 'service/collection';\nimport { toast } from 'react-toastify';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES, OBJ_TYPES } from 'constants/Common';\nimport TextInput from 'components/atoms/common/TextInput';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { isEmpty } from 'lodash';\nimport { flattenItems } from 'stores/utils';\nimport { Scrollbars } from 'react-custom-scrollbars';\n\nconst NewFlowTestModal = ({ closeFn = () => null, open = false }) => {\n  const { ipcRenderer } = window;\n  const collections = useCollectionStore.getState().collections;\n\n  const [flowtestName, setFlowtestName] = useState('');\n  const [selectedCollection, setSelectionCollection] = useState({});\n  const [selectedFolder, setSelectedFolder] = useState({});\n\n  // Error flags\n  const [showFlowtestNameError, setShowFlowtestNameError] = useState(false);\n  const [showCollectionSelectionError, setShowCollectionSelectionError] = useState(false);\n  //const [showFolderSelectionError, setShowFolderSelectionError] = useState(false);\n  const containsFolder = (collection) => {\n    const items = collection.items;\n    let haveFolderItem = false;\n    items.map((item) => {\n      if (item.type === OBJ_TYPES.folder) {\n        haveFolderItem = true;\n        return;\n      }\n    });\n    return haveFolderItem;\n  };\n\n  const resetFields = () => {\n    setFlowtestName('');\n    setSelectionCollection({});\n    setSelectedFolder({});\n    setShowFlowtestNameError(false);\n    setShowCollectionSelectionError(false);\n    //setShowFolderSelectionError(false);\n  };\n  return (\n    <div>\n      <Transition appear show={open} as={Fragment}>\n        <Dialog\n          as='div'\n          className='relative z-10'\n          onClose={() => {\n            resetFields();\n            closeFn();\n          }}\n        >\n          <Transition.Child\n            as={Fragment}\n            enter='ease-out duration-300'\n            enterFrom='opacity-0'\n            enterTo='opacity-100'\n            leave='ease-in duration-200'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            <div className='fixed inset-0 bg-black/25' />\n          </Transition.Child>\n\n          <div className='fixed inset-0 overflow-y-auto'>\n            <div className='flex min-h-full items-center justify-center p-4 text-center'>\n              <Transition.Child\n                as={Fragment}\n                enter='ease-out duration-300'\n                enterFrom='opacity-0 scale-95'\n                enterTo='opacity-100 scale-100'\n                leave='ease-in duration-200'\n                leaveFrom='opacity-100 scale-100'\n                leaveTo='opacity-0 scale-95'\n              >\n                <Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all'>\n                  <Dialog.Title as='h3' className='border-b border-gray-300 pb-4 text-center text-lg font-semibold'>\n                    {`Create a new Flowtest`}\n                  </Dialog.Title>\n                  <Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200} autoHeight autoHeightMax={600}>\n                    <div className='flex flex-col gap-6 py-6'>\n                      <div>\n                        <TextInput\n                          placeHolder={`Name`}\n                          onChangeHandler={(event) => {\n                            const flowtestName = event.target.value;\n                            setFlowtestName(flowtestName);\n                          }}\n                          name={'flowtest-name'}\n                        />\n                        {flowtestName.trim() === '' && showFlowtestNameError ? (\n                          <div className='py-2 text-red-600'>Please provide a name for your new flowtest</div>\n                        ) : (\n                          ''\n                        )}\n                      </div>\n                      <div>\n                        <div className='flex justify-between gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light text-cyan-900 transition hover:bg-background'>\n                          <Listbox\n                            value={selectedCollection}\n                            onChange={(value) => {\n                              setSelectionCollection(value);\n                              // setSelectionCollectionId(value.id);\n                            }}\n                          >\n                            <div className='relative flex h-full w-full'>\n                              <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                                {!isEmpty(selectedCollection) ? selectedCollection.name : 'Select Collection'}\n                                <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                              </Listbox.Button>\n                              <Transition\n                                as={Fragment}\n                                leave='transition ease-in duration-100'\n                                leaveFrom='opacity-100'\n                                leaveTo='opacity-0'\n                              >\n                                <Listbox.Options className='absolute right-0 top-10 z-10 mt-1 max-h-60 w-full overflow-auto rounded border border-cyan-900 bg-background-light py-1 text-base shadow-lg ring-1 ring-black/5'>\n                                  {collections.map((collection, index) => {\n                                    return (\n                                      <Listbox.Option\n                                        key={index}\n                                        className={({ active }) =>\n                                          `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                            active ? 'bg-background text-slate-900' : ''\n                                          }`\n                                        }\n                                        value={collection}\n                                      >\n                                        {({ selected }) => (\n                                          <>\n                                            <span\n                                              className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}\n                                            >\n                                              {collection.name}\n                                            </span>\n                                            {selected ? (\n                                              <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                                <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                                              </span>\n                                            ) : null}\n                                          </>\n                                        )}\n                                      </Listbox.Option>\n                                    );\n                                  })}\n                                </Listbox.Options>\n                              </Transition>\n                            </div>\n                          </Listbox>\n                        </div>\n                        {isEmpty(selectedCollection) && showCollectionSelectionError ? (\n                          <div className='py-2 text-red-600'>\n                            Please provide a collection in which you wants to create your new flowtest\n                          </div>\n                        ) : (\n                          ''\n                        )}\n                      </div>\n\n                      {!isEmpty(selectedCollection) && containsFolder(selectedCollection) ? (\n                        <div>\n                          <div className='flex justify-between gap-2 whitespace-nowrap rounded border border-cyan-900 bg-background-light text-cyan-900 transition hover:bg-background'>\n                            <Listbox\n                              value={selectedFolder}\n                              onChange={(value) => {\n                                setSelectedFolder(value);\n                              }}\n                            >\n                              <div className='relative flex h-full w-full'>\n                                <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                                  {!isEmpty(selectedFolder) ? selectedFolder.name : 'Select Folder'}\n                                  <ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />\n                                </Listbox.Button>\n                                <Transition\n                                  as={Fragment}\n                                  leave='transition ease-in duration-100'\n                                  leaveFrom='opacity-100'\n                                  leaveTo='opacity-0'\n                                >\n                                  <Listbox.Options className='absolute right-0 top-10 z-10 mt-1 max-h-60 w-full overflow-auto rounded border border-cyan-900 bg-background-light py-1 text-base shadow-lg ring-1 ring-black/5'>\n                                    {flattenItems(selectedCollection.items).map((collectionItem, index) => {\n                                      if (collectionItem.type === OBJ_TYPES.folder) {\n                                        return (\n                                          <Listbox.Option\n                                            key={index}\n                                            className={({ active }) =>\n                                              `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                                active ? 'bg-background text-slate-900' : ''\n                                              }`\n                                            }\n                                            value={collectionItem}\n                                          >\n                                            {({ selected }) => (\n                                              <>\n                                                <span\n                                                  className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}\n                                                >\n                                                  {ipcRenderer.relative(\n                                                    selectedCollection.pathname,\n                                                    collectionItem.pathname,\n                                                  )}\n                                                </span>\n                                                {selected ? (\n                                                  <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                                    <CheckIcon className='h-5 w-5' aria-hidden='true' />\n                                                  </span>\n                                                ) : null}\n                                              </>\n                                            )}\n                                          </Listbox.Option>\n                                        );\n                                      }\n                                    })}\n                                  </Listbox.Options>\n                                </Transition>\n                              </div>\n                            </Listbox>\n                          </div>\n                          {/* {selectedFolder.trim() === '' && showFolderSelectionError ? (\n                          <div className='py-2 text-red-600'>\n                            Please provide a folder in which you want to create your new flowtest\n                          </div>\n                        ) : (\n                          ''\n                        )} */}\n                        </div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                    <div className='mt-6 flex items-center gap-2'>\n                      <Button\n                        btnType={BUTTON_TYPES.secondary}\n                        intentType={BUTTON_INTENT_TYPES.error}\n                        isDisabled={false}\n                        onClickHandle={() => {\n                          resetFields();\n                          closeFn();\n                        }}\n                        fullWidth={true}\n                      >\n                        Cancel\n                      </Button>\n                      <Button\n                        btnType={BUTTON_TYPES.primary}\n                        isDisabled={false}\n                        onClickHandle={() => {\n                          let pathName = '';\n                          if (!flowtestName || flowtestName.trim() === '') {\n                            setShowFlowtestNameError(true);\n                            return;\n                          }\n                          if (!selectedCollection || !selectedCollection.id || selectedCollection.id === '') {\n                            setShowCollectionSelectionError(true);\n                            return;\n                          }\n                          if (!selectedFolder || !selectedFolder.pathname || selectedFolder.pathname === '') {\n                            pathName = selectedCollection.pathname;\n                          } else {\n                            pathName = selectedFolder.pathname;\n                          }\n                          setShowFlowtestNameError(false);\n                          setShowCollectionSelectionError(false);\n                          //setShowFolderSelectionError(false);\n\n                          createFlowTest(flowtestName, pathName, selectedCollection.id)\n                            .then((result) => {\n                              toast.success(`Created a new flowtest: ${flowtestName}`);\n                            })\n                            .catch((error) => {\n                              console.log(`Error creating new flowtest: ${error}`);\n                              toast.error(`Error creating new flowtest`);\n                              closeFn();\n                            });\n                          resetFields();\n                          closeFn();\n                        }}\n                        fullWidth={true}\n                      >\n                        Create\n                      </Button>\n                    </div>\n                  </Scrollbars>\n                </Dialog.Panel>\n              </Transition.Child>\n            </div>\n          </div>\n        </Dialog>\n      </Transition>\n    </div>\n  );\n};\n\nexport default NewFlowTestModal;\n"
  },
  {
    "path": "src/components/molecules/modals/sidebar/NewEnvironmentFileModal.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { Dialog, Transition, Listbox } from '@headlessui/react';\nimport { createEnvironmentFile } from 'service/collection';\nimport { toast } from 'react-toastify';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES, OBJ_TYPES } from 'constants/Common';\nimport TextInput from 'components/atoms/common/TextInput';\nimport useCollectionStore from 'stores/CollectionStore';\nimport { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';\nimport { isEmpty } from 'lodash';\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';\nimport { ChevronDownIcon } from '@heroicons/react/20/solid';\n\nconst NewEnvironmentFileModal = ({ closeFn = () => null, open = false }) => {\n  const collections = useCollectionStore.getState().collections;\n\n  const [environmentFileName, setEnvironmentFileName] = useState('');\n  const [selectedCollection, setSelectionCollection] = useState({});\n  const [showEnvironmentFileNameError, setShowEnvironmentFileNameError] = useState(false);\n  const [showCollectionSelectionError, setShowCollectionSelectionError] = useState(false);\n\n  const resetFields = () => {\n    setEnvironmentFileName('');\n    setSelectionCollection({});\n    setShowEnvironmentFileNameError(false);\n    setShowCollectionSelectionError(false);\n  };\n\n  return (\n    <div>\n      <Transition appear show={open} as={Fragment}>\n        <Dialog\n          as='div'\n          className='relative z-10'\n          onClose={() => {\n            resetFields();\n            closeFn();\n          }}\n        >\n          <Transition.Child\n            as={Fragment}\n            enter='ease-out duration-300'\n            enterFrom='opacity-0'\n            enterTo='opacity-100'\n            leave='ease-in duration-200'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            <div className='fixed inset-0 bg-black/25' />\n          </Transition.Child>\n\n          <div className='fixed inset-0 overflow-y-auto'>\n            <div className='flex items-center justify-center min-h-full p-4 text-center'>\n              <Transition.Child\n                as={Fragment}\n                enter='ease-out duration-300'\n                enterFrom='opacity-0 scale-95'\n                enterTo='opacity-100 scale-100'\n                leave='ease-in duration-200'\n                leaveFrom='opacity-100 scale-100'\n                leaveTo='opacity-0 scale-95'\n              >\n                <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                  <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                    {`Create a new environment file`}\n                  </Dialog.Title>\n                  <div className='flex flex-col gap-6 py-6'>\n                    <div>\n                      <TextInput\n                        placeHolder={`File Name`}\n                        onChangeHandler={(event) => {\n                          const fileName = event.target.value;\n                          setEnvironmentFileName(fileName);\n                        }}\n                        name={'environment-file-name'}\n                      />\n                      {environmentFileName.trim() === '' && showEnvironmentFileNameError ? (\n                        <div className='py-2 text-red-600'>Please provide a name for your new environment file</div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                    <div>\n                      <div className='flex justify-between gap-2 transition border rounded whitespace-nowrap border-cyan-900 bg-background-light text-cyan-900 hover:bg-background'>\n                        <Listbox\n                          value={selectedCollection}\n                          onChange={(value) => {\n                            setSelectionCollection(value);\n                          }}\n                        >\n                          <div className='relative flex w-full h-full'>\n                            <Listbox.Button className='flex w-full items-center justify-between gap-1 px-4 py-2.5 sm:text-sm'>\n                              {!isEmpty(selectedCollection) ? selectedCollection.name : 'Select Collection'}\n                              <ChevronUpDownIcon className='w-5 h-5' aria-hidden='true' />\n                            </Listbox.Button>\n                            <Transition\n                              as={Fragment}\n                              leave='transition ease-in duration-100'\n                              leaveFrom='opacity-100'\n                              leaveTo='opacity-0'\n                            >\n                              <Listbox.Options className='absolute right-0 z-10 w-full py-1 mt-1 overflow-auto text-base bg-white rounded shadow-lg top-10 max-h-60 ring-1 ring-black/5'>\n                                {collections.map((collection, index) => {\n                                  return (\n                                    <Listbox.Option\n                                      key={index}\n                                      className={({ active }) =>\n                                        `relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${\n                                          active ? 'bg-background-light text-slate-900' : ''\n                                        }`\n                                      }\n                                      value={collection}\n                                    >\n                                      {({ selected }) => (\n                                        <>\n                                          <span\n                                            className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}\n                                          >\n                                            {collection.name}\n                                          </span>\n                                          {selected ? (\n                                            <span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>\n                                              <CheckIcon className='w-5 h-5' aria-hidden='true' />\n                                            </span>\n                                          ) : null}\n                                        </>\n                                      )}\n                                    </Listbox.Option>\n                                  );\n                                })}\n                              </Listbox.Options>\n                            </Transition>\n                          </div>\n                        </Listbox>\n                      </div>\n                      {isEmpty(selectedCollection) && showCollectionSelectionError ? (\n                        <div className='py-2 text-red-600'>\n                          Please provide a collection in which you wants to create your new environment file\n                        </div>\n                      ) : (\n                        ''\n                      )}\n                    </div>\n                  </div>\n                  <div className='flex items-center gap-2 mt-6'>\n                    <Button\n                      btnType={BUTTON_TYPES.secondary}\n                      intentType={BUTTON_INTENT_TYPES.error}\n                      isDisabled={false}\n                      onClickHandle={() => {\n                        resetFields();\n                        closeFn();\n                      }}\n                      fullWidth={true}\n                    >\n                      Cancel\n                    </Button>\n                    <Button\n                      btnType={BUTTON_TYPES.primary}\n                      isDisabled={false}\n                      onClickHandle={() => {\n                        if (!environmentFileName || environmentFileName.trim() === '') {\n                          setShowEnvironmentFileNameError(true);\n                          return;\n                        }\n                        if (!selectedCollection || !selectedCollection.id || selectedCollection.id === '') {\n                          setShowCollectionSelectionError(true);\n                          return;\n                        }\n                        setShowEnvironmentFileNameError(false);\n                        setShowCollectionSelectionError(false);\n                        createEnvironmentFile(environmentFileName, selectedCollection.id)\n                          .then((result) => {\n                            toast.success(`Created a new environment: ${environmentFileName}`);\n                          })\n                          .catch((error) => {\n                            console.log(`Error creating new environment: ${error}`);\n                            toast.error(`Error while creating new environment`);\n                            closeFn();\n                          });\n                        resetFields();\n                        closeFn();\n                      }}\n                      fullWidth={true}\n                    >\n                      Create\n                    </Button>\n                  </div>\n                </Dialog.Panel>\n              </Transition.Child>\n            </div>\n          </div>\n        </Dialog>\n      </Transition>\n    </div>\n  );\n};\n\nexport default NewEnvironmentFileModal;\n"
  },
  {
    "path": "src/components/molecules/modals/sidebar/NewLabelModal.js",
    "content": "import React, { Fragment, useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { Dialog, Transition } from '@headlessui/react';\nimport { createFolder, createFlowTest, createEnvironmentFile, cloneFlowTest } from 'service/collection';\nimport { DirectoryOptionsActions } from 'constants/WorkspaceDirectory';\nimport { toast } from 'react-toastify';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport TextInput from 'components/atoms/common/TextInput';\n\nconst NewLabelModal = ({ closeFn = () => null, open = false, pathName, collectionId, menuOption }) => {\n  const [labelValue, setLabelValue] = useState('');\n  const [showLabelValueError, setShowLabelValueError] = useState(false);\n\n  const resetFields = () => {\n    setLabelValue('');\n    setShowLabelValueError(false);\n  };\n  return (\n    <div>\n      <Transition appear show={open} as={Fragment}>\n        <Dialog\n          as='div'\n          className='relative z-10'\n          onClose={() => {\n            resetFields();\n            closeFn();\n          }}\n        >\n          <Transition.Child\n            as={Fragment}\n            enter='ease-out duration-300'\n            enterFrom='opacity-0'\n            enterTo='opacity-100'\n            leave='ease-in duration-200'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            <div className='fixed inset-0 bg-black/25' />\n          </Transition.Child>\n\n          <div className='fixed inset-0 overflow-y-auto'>\n            <div className='flex items-center justify-center min-h-full p-4 text-center'>\n              <Transition.Child\n                as={Fragment}\n                enter='ease-out duration-300'\n                enterFrom='opacity-0 scale-95'\n                enterTo='opacity-100 scale-100'\n                leave='ease-in duration-200'\n                leaveFrom='opacity-100 scale-100'\n                leaveTo='opacity-0 scale-95'\n              >\n                <Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white rounded shadow-xl'>\n                  <Dialog.Title as='h3' className='pb-4 text-lg font-semibold text-center border-b border-gray-300'>\n                    {`Create a ${menuOption}`}\n                  </Dialog.Title>\n                  <div className='mt-6'>\n                    <TextInput\n                      placeHolder={`label`}\n                      onChangeHandler={(event) => {\n                        const labelValue = event.target.value;\n                        setLabelValue(labelValue);\n                      }}\n                      name={'label'}\n                    />\n                    {showLabelValueError ? <div className='py-2 text-red-600'>Please provide a label value</div> : ''}\n                  </div>\n                  <div className='flex items-center gap-2 mt-6'>\n                    <Button\n                      btnType={BUTTON_TYPES.secondary}\n                      intentType={BUTTON_INTENT_TYPES.error}\n                      isDisabled={false}\n                      onClickHandle={() => {\n                        resetFields();\n                        closeFn();\n                      }}\n                      fullWidth={true}\n                    >\n                      Cancel\n                    </Button>\n                    <Button\n                      btnType={BUTTON_TYPES.primary}\n                      isDisabled={false}\n                      onClickHandle={() => {\n                        if (!labelValue || labelValue === '') {\n                          setShowLabelValueError(true);\n                          return;\n                        }\n                        console.log(\n                          `modalType :: ${menuOption} :: labelValue :: ${labelValue} :: pathName :: ${pathName} :: collectionId :: ${collectionId}`,\n                        );\n                        if (menuOption === 'new-folder') {\n                          createFolder(labelValue, pathName, collectionId)\n                            .then((result) => {\n                              console.log(\n                                `Created a new folder: name = ${labelValue}, path = ${pathName}, collectionId = ${collectionId}, result: ${result} \\n`,\n                              );\n                              toast.success(`Created a new folder: ${labelValue}`);\n                            })\n                            .catch((error) => {\n                              console.log(`Error creating new folder: ${error}`);\n                              toast.error(`Error creating new folder`);\n                              closeFn();\n                            });\n                        } else if (menuOption === 'new-flow') {\n                          createFlowTest(labelValue, pathName, collectionId)\n                            .then((result) => {\n                              console.log(\n                                `Created a new flowtest: name = ${labelValue}, path = ${pathName}, collectionId = ${collectionId}, result: ${result} \\n`,\n                              );\n                              toast.success(`Created a new flowtest: ${labelValue}`);\n                            })\n                            .catch((error) => {\n                              console.log(`Error creating new flowtest: ${error}`);\n                              toast.error(`Error creating new flowtest`);\n                              closeFn();\n                            });\n                        } else if (menuOption === 'clone-flow') {\n                          console.log(labelValue);\n                          console.log(pathName);\n                          cloneFlowTest(labelValue, pathName, collectionId)\n                            .then((result) => {\n                              console.log(\n                                `Cloned a new flowtest: name = ${labelValue}, path = ${pathName}, collectionId = ${collectionId}, result: ${result} \\n`,\n                              );\n                              toast.success(`Cloned the flowtest: ${labelValue}`);\n                            })\n                            .catch((error) => {\n                              console.log(`Error cloning flowtest: ${error}`);\n                              toast.error(`Error cloning flowtest`);\n                              closeFn();\n                            });\n                        } else if (menuOption === 'collection') {\n                          // createCollection();\n                          // wont be needing it here but just putting it for testing\n                          console.log(`\\n Creating a new collection by the name : ${labelValue} \\n`);\n                        } else if (menuOption === DirectoryOptionsActions.addNewEnvironment.value) {\n                          createEnvironmentFile(labelValue, collectionId)\n                            .then((result) => {\n                              console.log(\n                                `Created a new environment: name = ${labelValue}, collectionId = ${collectionId}, result: ${result} \\n`,\n                              );\n                              toast.success(`Created a new environment: ${labelValue}`);\n                            })\n                            .catch((error) => {\n                              console.log(`Error creating new environment: ${error}`);\n                              toast.error(`Error while creating new environment`);\n                              closeFn();\n                            });\n                        }\n                        resetFields();\n                        closeFn();\n                      }}\n                      fullWidth={true}\n                    >\n                      Create\n                    </Button>\n                  </div>\n                </Dialog.Panel>\n              </Transition.Child>\n            </div>\n          </div>\n        </Dialog>\n      </Transition>\n    </div>\n  );\n};\n\nNewLabelModal.propTypes = {\n  closeFn: PropTypes.func.isRequired,\n  open: PropTypes.boolean.isRequired,\n  pathName: PropTypes.string.isRequired,\n  collectionId: PropTypes.string.isRequired,\n  menuOption: PropTypes.string.isRequired,\n};\n\nexport default NewLabelModal;\n"
  },
  {
    "path": "src/components/molecules/sideSheets/FlowLogs.js",
    "content": "import React, { useState } from 'react';\nimport {\n  ShieldCheckIcon,\n  BarsArrowUpIcon,\n  ExclamationTriangleIcon,\n  XCircleIcon,\n  CheckCircleIcon,\n} from '@heroicons/react/24/outline';\nimport { JsonView, collapseAllNested, defaultStyles } from 'react-json-view-lite';\nimport { LogLevel } from '../flow/graph/GraphLogger';\nimport { ClockIcon } from '@heroicons/react/20/solid';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_INTENT_TYPES, BUTTON_TYPES } from 'constants/Common';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\nimport SettingsModal from '../modals/SettingsModal';\nimport { formatTimeStamp } from 'utils/common';\n\nconst FlowLogs = ({ logsData }) => {\n  const [openSettingsModal, setOpenSettingsModal] = useState(false);\n\n  const renderFlowScan = (flowScan) => {\n    if (flowScan.upload === 'disabled') {\n      return (\n        <div className='my-2 w-full rounded border border-sky-600 bg-sky-50 p-4 text-sky-600'>\n          <div className='flex w-full items-center justify-between'>\n            <div className='flex w-full items-center justify-start gap-4'>\n              <BarsArrowUpIcon className='h-7 w-7' />\n              <div className='w-full'>\n                <div className='flex items-center justify-start gap-2 text-lg font-semibold'>\n                  <h2> {flowScan?.message} </h2>\n                </div>\n              </div>\n            </div>\n            <Button\n              btnType={BUTTON_TYPES.secondary}\n              intentType={BUTTON_INTENT_TYPES.info}\n              isDisabled={false}\n              onClickHandle={() => setOpenSettingsModal(true)}\n            >\n              Activate Scans\n            </Button>\n            <SettingsModal closeFn={() => setOpenSettingsModal(false)} open={openSettingsModal} initialTab={0} />\n          </div>\n        </div>\n      );\n    } else if (flowScan.upload === 'success') {\n      return (\n        <div className='my-2 w-full rounded border border-green-600 bg-green-50 p-4 text-green-600'>\n          <div className='flex w-full items-center justify-start gap-4'>\n            <ShieldCheckIcon className='h-7 w-7' />\n            <div className='w-full'>\n              <div className='mb-4 flex items-center justify-start gap-2 text-lg font-semibold'>\n                <h2>Successfully published the scan</h2>\n              </div>\n              <HorizontalDivider themeColor={'bg-green-600'} />\n              <p className='font pb-2 pt-4'>\n                <span className='font-semibold'>URL: </span>\n                <a href={flowScan.url} target='_blank' rel='noreferrer' className='hover:underline'>\n                  {flowScan.url}\n                </a>\n              </p>\n            </div>\n          </div>\n        </div>\n      );\n    } else if (flowScan.upload === 'fail') {\n      return (\n        <div className='my-2 flex w-full items-center gap-4 rounded border border-amber-600 bg-amber-50 p-4 text-amber-600'>\n          <div className='flex items-center'>\n            <ExclamationTriangleIcon className='h-7 w-7' />\n          </div>\n          <div className='w-full'>\n            <div\n              className={`flex items-center justify-start gap-2 text-lg font-semibold ${flowScan?.reason ? 'mb-4' : 'mb-0'}`}\n            >\n              <h2> {flowScan.message} </h2>\n            </div>\n            {flowScan?.reason ? (\n              <>\n                <HorizontalDivider themeColor={'bg-amber-600'} />\n                <p className='pb-2 pt-4'>{flowScan?.reason}</p>\n              </>\n            ) : (\n              <></>\n            )}\n          </div>\n        </div>\n      );\n    }\n  };\n\n  return (\n    <div className='overflow-auto'>\n      <div>{renderFlowScan(logsData.run.scan)}</div>\n      <div className='mt-4 flex flex-col rounded-md border-2 border-slate-300 bg-background-light text-cyan-900 shadow-sm'>\n        <h2 className='border-b-2 border-slate-300 px-4 py-2 text-2xl font-medium'>\n          <div className='flex flex-row items-center gap-2'>\n            Logs\n            {logsData.run.status === 'Success' ? (\n              <CheckCircleIcon className='h-5 w-5' />\n            ) : (\n              <XCircleIcon className='h-5 w-5' />\n            )}\n          </div>\n        </h2>\n        <div className='p-4'>\n          {logsData.run.logs.map((log, index) => {\n            if (log.logLevel === LogLevel.INFO) {\n              let message = '';\n              let json = undefined;\n              if (log.message.trim() != '') {\n                message = log.message;\n              }\n\n              if (log.node != undefined) {\n                const type = log.node.type;\n                const data = log.node.data;\n                if (type === 'outputNode') {\n                  json = {\n                    output: data.output,\n                  };\n                }\n\n                if (type === 'authNode') {\n                  message = `${data.authType}`;\n                }\n\n                if (type === 'assertNode') {\n                  message = `Assert : ${data.var1} of type ${typeof data.var1} ${data.operator} ${data.var2} of type ${typeof data.var2} = ${data.result}`;\n                }\n\n                if (type === 'delayNode') {\n                  message = `Waiting for ${data.delay} ms`;\n                }\n\n                if (type === 'setVarNode') {\n                  message = `Setting Variable:  ${data.name} = ${data.value}`;\n                }\n\n                if (type === 'requestNode') {\n                  message = `${data.request.method.toUpperCase()} ${data.request.url}`;\n                  json = data;\n                }\n              }\n\n              return (\n                <>\n                  <ul className='menu w-full p-0' key={index}>\n                    <li>\n                      <div className='flex items-center justify-between gap-2 text-balance rounded p-0 text-start transition duration-200 ease-out'>\n                        <div className='flex items-center justify-start gap-2 px-2 py-1'>\n                          <ClockIcon className='h-6 w-6' />\n                          <span className='text-lg'>{`${log.timestamp} : ${message}`}</span>\n                        </div>\n                      </div>\n                      <ul className='flow-logs-menu before:absolute before:bottom-0 before:top-0 before:w-[1px] before:bg-background-dark before:opacity-100'>\n                        <li className='pl-4 pr-2'>\n                          {json != undefined ? (\n                            <div className='json-view-container my-4 w-full overflow-auto border border-slate-700 px-2 py-4'>\n                              <React.Fragment>\n                                <JsonView data={json} shouldExpandNode={collapseAllNested} style={defaultStyles} />\n                              </React.Fragment>\n                            </div>\n                          ) : (\n                            <></>\n                          )}\n                        </li>\n                      </ul>\n                    </li>\n                  </ul>\n                </>\n              );\n            } else {\n              return (\n                <>\n                  <ul className='menu w-full p-0' key={index}>\n                    <li>\n                      <div className='flex items-center justify-between gap-2 text-balance rounded p-0 text-start transition duration-200 ease-out'>\n                        <div className='flex items-center justify-start gap-2 px-2 py-1 text-red-500'>\n                          <ClockIcon className='h-6 w-6' />\n                          <span className='text-lg'>{`${log.timestamp} : ${log.message}`}</span>\n                        </div>\n                      </div>\n                      <ul className='flow-logs-menu before:absolute before:bottom-0 before:top-0 before:w-[1px] before:bg-background-dark before:opacity-100'>\n                        <li className='pl-4 pr-2'>\n                          {log.node != undefined ? (\n                            <div className='json-view-container my-4 overflow-auto border border-slate-700 px-2 py-4'>\n                              <React.Fragment>\n                                <JsonView\n                                  data={log.node.data}\n                                  shouldExpandNode={collapseAllNested}\n                                  style={defaultStyles}\n                                />\n                              </React.Fragment>\n                            </div>\n                          ) : (\n                            <></>\n                          )}\n                        </li>\n                      </ul>\n                    </li>\n                  </ul>\n                </>\n              );\n            }\n          })}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default FlowLogs;\n"
  },
  {
    "path": "src/components/molecules/sidebar/Empty.js",
    "content": "import React, { useState } from 'react';\nimport { FolderArrowDownIcon } from '@heroicons/react/24/outline';\nimport { PlusIcon } from '@heroicons/react/20/solid';\nimport { BUTTON_TYPES } from 'constants/Common';\nimport ImportCollectionModal from 'components/molecules/modals/ImportCollectionModal';\nimport Button from 'components/atoms/common/Button';\nimport OpenCollectionModal from '../modals/OpenCollectionModal';\n\nconst Empty = () => {\n  //const [newCollectionModalOpen, setNewCollectionModalOpen] = useState(false);\n  const [openCollectionModal, setOpenCollectionModal] = useState(false);\n  const [importCollectionModal, setImportCollectionModal] = useState(false);\n  return (\n    <>\n      <div className='flex flex-col items-center justify-center p-4'>\n        <div className='text-xs font-medium'>Open or Import a collection</div>\n        <div className='flex flex-col items-stretch gap-4 mt-4'>\n          <Button btnType={BUTTON_TYPES.primary} isDisabled={false} onClickHandle={() => setOpenCollectionModal(true)}>\n            <FolderArrowDownIcon className='w-4 h-4' />\n            <span className='font-semibold'>Open</span>\n          </Button>\n          <Button\n            btnType={BUTTON_TYPES.primary}\n            isDisabled={false}\n            onClickHandle={() => setImportCollectionModal(true)}\n          >\n            <PlusIcon className='w-4 h-4' />\n            <span className='font-semibold'>Import</span>\n          </Button>\n        </div>\n      </div>\n      <div>\n        <OpenCollectionModal closeFn={() => setOpenCollectionModal(false)} open={openCollectionModal} />\n        <ImportCollectionModal closeFn={() => setImportCollectionModal(false)} open={importCollectionModal} />\n      </div>\n    </>\n  );\n};\n\nexport default Empty;\n"
  },
  {
    "path": "src/components/molecules/sidebar/content/Collection.js",
    "content": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { ArchiveBoxIcon, FolderIcon, DocumentIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { FLOW_FILE_SUFFIX_REGEX, OBJ_TYPES } from 'constants/Common';\nimport { readFlowTest } from 'service/collection';\nimport OptionsMenu from 'components/atoms/sidebar/collections/OptionsMenu';\nimport { toast } from 'react-toastify';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport useCollectionStore from 'stores/CollectionStore';\n\nconst Collection = ({ collectionId, item, depth }) => {\n  //const [isExpanded, setIsExpanded] = useState(false);\n  const clickItem = useCollectionStore((state) => state.clickItem);\n\n  const getListDisplayTitle = () => {\n    if (item.type === OBJ_TYPES.collection) {\n      // this is for collections tab thus we have archive box icon\n      return (\n        <div\n          className='flex items-center justify-between gap-2 p-0 transition duration-200 ease-out rounded text-balance text-start hover:bg-background-light'\n          onClick={(event) => {\n            const clickFromElementDataSet = event.target.dataset;\n            const clickFrom = clickFromElementDataSet?.clickFrom;\n            if (!clickFrom || clickFrom !== 'options-menu') {\n              clickItem(item, collectionId);\n              //return setIsExpanded((prev) => !prev);\n            }\n          }}\n        >\n          <Tippy content={item.pathname} placement='top'>\n            <div className='flex items-center justify-start gap-2 px-2 py-1'>\n              <ArchiveBoxIcon className='w-4 h-4' />\n              <span>{item.name}</span>\n            </div>\n          </Tippy>\n\n          <OptionsMenu\n            data-click-from='options-menu'\n            directory={item}\n            data-item-type={OBJ_TYPES.collection}\n            itemType={OBJ_TYPES.collection}\n            collectionId={collectionId}\n          />\n        </div>\n      );\n    }\n\n    if (item.type === OBJ_TYPES.flowtest && item.name.match(FLOW_FILE_SUFFIX_REGEX)) {\n      return (\n        <div\n          className='flex items-center justify-between gap-2 p-0 transition duration-200 ease-out rounded text-balance text-start hover:bg-background-light'\n          onClick={() => {\n            readFlowTest(item.pathname, collectionId)\n              .then((result) => {\n                console.log(\n                  `Read flowtest: name = ${item.name}, path = ${item.pathname}, collectionId = ${collectionId}, result: ${result}`,\n                );\n              })\n              .catch((error) => {\n                console.log(`Error reading flowtest: ${error}`);\n                toast.error(`Error reading flowtest`);\n              });\n          }}\n        >\n          <div className='flex items-center justify-start gap-2 px-2 py-1'>\n            <DocumentIcon className='w-4 h-4' />\n            <span>{item.name}</span>\n          </div>\n          <OptionsMenu\n            data-click-from='options-menu'\n            directory={item}\n            data-item-type={OBJ_TYPES.flowtest}\n            itemType={OBJ_TYPES.flowtest}\n            collectionId={collectionId}\n          />\n        </div>\n      );\n    }\n\n    if (item.type === OBJ_TYPES.folder) {\n      return (\n        <div\n          className='flex items-center justify-between gap-2 p-0 transition duration-200 ease-out rounded text-balance text-start hover:bg-background-light'\n          onClick={(event) => {\n            const clickFrom = event.target.dataset?.clickFrom;\n            if (!clickFrom || clickFrom !== 'options-menu') {\n              clickItem(item, collectionId);\n              //return setIsExpanded((prev) => !prev);\n            }\n          }}\n        >\n          <div className='flex items-center justify-start gap-2 px-2 py-1'>\n            <FolderIcon className='w-4 h-4' />\n            <span data-type-name={item.type}>{item.name}</span>\n          </div>\n          <OptionsMenu\n            data-click-from='options-menu'\n            directory={item}\n            data-item-type={OBJ_TYPES.folder}\n            itemType={OBJ_TYPES.folder}\n            collectionId={collectionId}\n          />\n        </div>\n      );\n    }\n  };\n  return (\n    <>\n      <li>\n        {getListDisplayTitle()}\n        {item.collapsed === false && (\n          <>\n            {item.items?.map((childItem, index) => (\n              <ul\n                key={index}\n                className='before:absolute before:bottom-0 before:top-0 before:w-[1px] before:bg-background-dark before:opacity-100'\n              >\n                <Collection collectionId={collectionId} item={childItem} depth={depth + 1} />\n              </ul>\n            ))}\n          </>\n        )}\n      </li>\n    </>\n  );\n};\n\nCollection.propTypes = {\n  collectionId: PropTypes.string.isRequired,\n  item: PropTypes.object.isRequired,\n  depth: PropTypes.number.isRequired,\n};\n\nexport default Collection;\n"
  },
  {
    "path": "src/components/molecules/sidebar/content/Collections.js",
    "content": "import React, { useState } from 'react';\nimport { PropTypes } from 'prop-types';\nimport { OBJ_TYPES } from 'constants/Common';\nimport { DirectoryOptionsActions } from 'constants/WorkspaceDirectory';\nimport { deleteCollection, deleteFolder, deleteFlowTest } from 'service/collection';\nimport NewLabelModal from 'components/molecules/modals/sidebar/NewLabelModal';\nimport Collection from './Collection';\nimport ConfirmActionModal from 'components/molecules/modals/ConfirmActionModal';\nimport { toast } from 'react-toastify';\n\nconst Collections = ({ collections }) => {\n  const [newLabelModalOpen, setNewLabelModal] = useState(false);\n  const [confirmActionModalOpen, setConfirmActionModalOpen] = useState(false);\n\n  const [selectedMenuItem, setSelectedMenuItem] = useState('');\n  const [selectedPathName, setSelectedPathName] = useState('');\n  const [selectedCollectionId, setSelectedCollectionId] = useState('');\n  const [selectedItemType, setSelectedItemType] = useState('');\n\n  const messageForConfirmActionModal =\n    selectedItemType === OBJ_TYPES.collection\n      ? `Do you wish to remove this collection? This action does not delete from disk so you can open this collection again`\n      : `Do you wish to delete this ${selectedItemType}? This action deletes it from disk and cannot be undone`;\n\n  const handleDeleteMenuItem = (menuItemType, path, collectionId) => {\n    if (menuItemType === OBJ_TYPES.collection) {\n      deleteCollection(collectionId)\n        .then((result) => {\n          console.log(`Deleted collection: collectionId = ${collectionId}, result: ${result}`);\n        })\n        .catch((error) => {\n          console.log(`Error deleting collection = ${collectionId}: ${error}`);\n          toast.error(`Error deleting collection`);\n        });\n    }\n\n    if (menuItemType === OBJ_TYPES.folder) {\n      deleteFolder(path, collectionId)\n        .then((result) => {\n          console.log(`Deleted folder: path = ${path}, collectionId = ${collectionId}, result: ${result}`);\n        })\n        .catch((error) => {\n          console.log(`Error deleting folder = ${path}: ${error}`);\n          toast.error(`Error deleting folder`);\n        });\n    }\n\n    if (menuItemType === OBJ_TYPES.flowtest) {\n      deleteFlowTest(path, collectionId)\n        .then((result) => {\n          console.log(`Deleted flowtest: path = ${path}, collectionId = ${collectionId}, result: ${result}`);\n        })\n        .catch((error) => {\n          console.log(`Error deleting flowtest = ${path}: ${error}`);\n          toast.error(`Error deleting flowtest`);\n        });\n    }\n  };\n\n  return (\n    <div\n      className='h-[87vh] flex-auto overflow-auto pb-14'\n      onClick={(event) => {\n        const clickFromElementDataSet = event.target.dataset;\n        const clickFrom = clickFromElementDataSet?.clickFrom;\n\n        if (clickFrom && clickFrom === 'options-menu') {\n          const itemType = clickFromElementDataSet?.itemType;\n          const optionsMenuItem = clickFromElementDataSet?.optionsMenuItem;\n          const pathName = clickFromElementDataSet?.pathName;\n          const collectionId = clickFromElementDataSet?.collectionId;\n\n          setSelectedPathName(pathName);\n          setSelectedCollectionId(collectionId);\n          setSelectedMenuItem(optionsMenuItem);\n\n          switch (optionsMenuItem) {\n            case DirectoryOptionsActions.addNewFolder.value:\n              setNewLabelModal(true);\n              break;\n            case DirectoryOptionsActions.addNewFlow.value:\n              setNewLabelModal(true);\n              break;\n            case DirectoryOptionsActions.cloneFlow.value:\n              setNewLabelModal(true);\n              break;\n            case DirectoryOptionsActions.delete.value:\n              setSelectedItemType(itemType);\n              setConfirmActionModalOpen(true);\n              break;\n            default:\n              // need to return an error here\n              console.log(`DEFAULT OPTION`);\n          }\n        }\n      }}\n    >\n      <ul className='w-full menu'>\n        {collections.map((collection) => (\n          <Collection key={collection.id} collectionId={collection.id} item={collection} depth={1} />\n        ))}\n      </ul>\n      <NewLabelModal\n        closeFn={() => setNewLabelModal(false)}\n        open={newLabelModalOpen}\n        pathName={selectedPathName}\n        collectionId={selectedCollectionId}\n        menuOption={selectedMenuItem}\n      />\n      <ConfirmActionModal\n        closeFn={() => setConfirmActionModalOpen(false)}\n        open={confirmActionModalOpen}\n        message={messageForConfirmActionModal}\n        actionFn={() => {\n          handleDeleteMenuItem(selectedItemType, selectedPathName, selectedCollectionId);\n          setConfirmActionModalOpen(false);\n        }}\n      />\n    </div>\n  );\n};\n\nCollections.propTypes = {\n  collections: PropTypes.array.isRequired,\n};\n\nexport default Collections;\n"
  },
  {
    "path": "src/components/molecules/sidebar/content/Environment.js",
    "content": "import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { ArchiveBoxIcon, DocumentIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { OBJ_TYPES } from 'constants/Common';\nimport { deleteEnvironmentFile, readEnvironmentFile } from 'service/collection';\nimport EnvOptionsMenu from 'components/atoms/sidebar/environments/EnvOptionsMenu';\nimport ConfirmActionModal from 'components/molecules/modals/ConfirmActionModal';\nimport { toast } from 'react-toastify';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport useCollectionStore from 'stores/CollectionStore';\n\nconst Environment = ({ collectionId, collection }) => {\n  //const [isExpanded, setIsExpanded] = useState(false);\n  const clickEnvironments = useCollectionStore((state) => state.clickEnvironments);\n  const [confirmActionModalOpen, setConfirmActionModalOpen] = useState(false);\n  const [envToDelete, setEnvToDelete] = useState('');\n\n  const messageForConfirmActionModal =\n    'Do you wish to delete this environment? This action deletes it from disk and cannot be undone';\n\n  return (\n    <>\n      <li>\n        <div\n          className='flex items-center justify-between gap-2 p-0 transition duration-200 ease-out rounded text-balance text-start hover:bg-background-light'\n          onClick={(event) => {\n            const clickFromElementDataSet = event.target.dataset;\n            const clickFrom = clickFromElementDataSet?.clickFrom;\n            if (!clickFrom || clickFrom !== 'env-options-menu') {\n              //return setIsExpanded((prev) => !prev);\n              clickEnvironments(collectionId);\n            }\n          }}\n        >\n          <Tippy content={collection.pathname} placement='top'>\n            <div className='flex items-center justify-start gap-2 px-2 py-1'>\n              <ArchiveBoxIcon className='w-4 h-4' />\n              <span>{collection.name}</span>\n            </div>\n          </Tippy>\n          <EnvOptionsMenu\n            data-click-from='env-options-menu'\n            itemType={OBJ_TYPES.environment}\n            pathName={collection.pathname}\n            collectionId={collectionId}\n          />\n        </div>\n        {collection.envCollapsed === false && (\n          <>\n            {collection.environments?.map((environment, index) => (\n              <ul\n                key={index}\n                className='before:absolute before:bottom-0 before:top-0 before:w-[1px] before:bg-background-dark before:opacity-100'\n              >\n                <li>\n                  <div\n                    className='flex flex-row items-center justify-between gap-2 p-0 transition duration-200 ease-out rounded text-balance text-start hover:bg-background-light'\n                    onClick={() => {\n                      try {\n                        readEnvironmentFile(environment.name, collectionId);\n                      } catch (error) {\n                        toast.error(`Error reading environment: ${environment.name}`);\n                      }\n                    }}\n                  >\n                    <div className='flex items-center justify-start gap-2 px-2 py-1 cursor-pointer hover:bg-transparent'>\n                      <DocumentIcon className='w-4 h-4' />\n                      <span>{environment.name}</span>\n                    </div>\n                    <div\n                      className='relative inline-block p-2 text-left transition duration-200 ease-out rounded rounded-l-none hover:bg-slate-200'\n                      onClick={() => {\n                        setEnvToDelete(environment.name);\n                        setConfirmActionModalOpen(true);\n                      }}\n                    >\n                      <TrashIcon className='w-4 h-4' aria-hidden='true' />\n                    </div>\n                  </div>\n                </li>\n              </ul>\n            ))}\n          </>\n        )}\n      </li>\n      <ConfirmActionModal\n        closeFn={() => setConfirmActionModalOpen(false)}\n        open={confirmActionModalOpen}\n        message={messageForConfirmActionModal}\n        actionFn={() => {\n          deleteEnvironmentFile(envToDelete, collectionId)\n            .then((result) => {\n              console.log(\n                `Deleted environment: name = ${envToDelete}, collectionId = ${collectionId}, result: ${result}`,\n              );\n            })\n            .catch((error) => {\n              console.log(\n                `Error deleting environment:  name = ${envToDelete}, collectionId = ${collectionId} and error: ${error}`,\n              );\n              toast.error(`Error deleting environment`);\n            });\n          setConfirmActionModalOpen(false);\n        }}\n      />\n    </>\n  );\n};\n\nEnvironment.propTypes = {\n  collectionId: PropTypes.string.isRequired,\n  collection: PropTypes.object.isRequired,\n};\n\nexport default Environment;\n"
  },
  {
    "path": "src/components/molecules/sidebar/content/Environments.js",
    "content": "import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport Environment from './Environment';\nimport NewLabelModal from 'components/molecules/modals/sidebar/NewLabelModal';\nimport { DirectoryOptionsActions } from 'constants/WorkspaceDirectory';\n\nconst Environments = ({ collections }) => {\n  const [newLabelModalOpen, setNewLabelModal] = useState(false);\n  const [selectedMenuItem, setSelectedMenuItem] = useState('');\n  const [selectedPathName, setSelectedPathName] = useState('');\n  const [selectedCollectionId, setSelectedCollectionId] = useState('');\n\n  return (\n    <div\n      className='h-[87vh] flex-auto overflow-auto pb-14'\n      onClick={(event) => {\n        const clickFromElementDataSet = event.target.dataset;\n        const clickFrom = clickFromElementDataSet?.clickFrom;\n\n        if (clickFrom && clickFrom === 'env-options-menu') {\n          // const itemType = clickFromElementDataSet?.itemType;\n          const optionsMenuItem = clickFromElementDataSet?.optionsMenuItem;\n          const pathName = clickFromElementDataSet?.pathName;\n          const collectionId = clickFromElementDataSet?.collectionId;\n\n          setSelectedPathName(pathName);\n          setSelectedCollectionId(collectionId);\n          setSelectedMenuItem(optionsMenuItem);\n\n          switch (optionsMenuItem) {\n            case DirectoryOptionsActions.addNewEnvironment.value:\n              setNewLabelModal(true);\n              break;\n            default:\n              // need to return an error here\n              console.log(`DEFAULT OPTION`);\n          }\n        }\n      }}\n    >\n      <ul className='menu w-full'>\n        {collections.map((collection) => (\n          <Environment key={collection.id} collectionId={collection.id} collection={collection} />\n        ))}\n      </ul>\n      <NewLabelModal\n        closeFn={() => setNewLabelModal(false)}\n        open={newLabelModalOpen}\n        pathName={selectedPathName}\n        collectionId={selectedCollectionId}\n        menuOption={selectedMenuItem}\n      />\n    </div>\n  );\n};\n\nEnvironments.propTypes = {\n  collections: PropTypes.array.isRequired,\n};\n\nexport default Environments;\n"
  },
  {
    "path": "src/components/molecules/sidebar/content/index.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport useNavigationStore from 'stores/AppNavBarStore';\nimport { AppNavBarItems } from 'constants/AppNavBar';\nimport Collections from './Collections';\nimport Environments from './Environments';\nimport SideBarSubHeader from 'components/molecules/headers/SideBarSubHeader';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\n\nconst Content = ({ collections }) => {\n  const navigationSelectedValue = useNavigationStore((state) => state.selectedNavVal);\n  return (\n    <>\n      {navigationSelectedValue === AppNavBarItems.collections.value ? (\n        <>\n          <SideBarSubHeader />\n          <HorizontalDivider />\n          <Collections collections={collections} />\n        </>\n      ) : (\n        <Environments collections={collections} />\n      )}\n    </>\n  );\n};\n\nContent.propTypes = {\n  collections: PropTypes.array.isRequired,\n};\n\nexport default Content;\n"
  },
  {
    "path": "src/components/molecules/workspace/EmptyWorkSpaceContent.js",
    "content": "import React, { useState } from 'react';\nimport Tippy from '@tippyjs/react';\nimport { RectangleStackIcon, Square3Stack3DIcon } from '@heroicons/react/24/solid';\nimport useCollectionStore from 'stores/CollectionStore';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\nimport Button from 'components/atoms/common/Button';\nimport { BUTTON_TYPES } from 'constants/Common';\nimport ImportCollectionModal from '../modals/ImportCollectionModal';\nimport GenerateFlowTestModal from '../modals/GenerateFlowTestModal';\nimport NewFlowTestModal from '../modals/flow/NewFlowTestModal';\nimport NewEnvironmentFileModal from '../modals/sidebar/NewEnvironmentFileModal';\nimport OpenCollectionModal from '../modals/OpenCollectionModal';\nimport GenAIUsageDisclaimer from '../modals/GenAIUsageDisclaimer';\nimport useSettingsStore from 'stores/SettingsStore';\n\nconst EmptyWorkSpaceContent = () => {\n  const collections = useCollectionStore((state) => state.collections);\n  const [openCollectionModal, setOpenCollectionModal] = useState(false);\n  const [importCollectionModal, setImportCollectionModal] = useState(false);\n  const [newFlowTestModal, setNewFlowTestModal] = useState(false);\n  const [generateFlowTestModalOpen, setGenerateFlowTestModalOpen] = useState(false);\n  const [genAiUsageDisclaimerModalOpen, setGenAiUsageDisclaimerModalOpen] = useState(false);\n  const [newEnvironmentFileModal, setNewEnvironmentFileModal] = useState(false);\n\n  return (\n    <div className='flex h-full items-center justify-center text-cyan-900'>\n      <div className='flex max-w-xl flex-col gap-8 2xl:gap-10'>\n        <div className='text-center'>\n          <div className='flex items-center justify-center'>\n            <RectangleStackIcon className='h-24 w-24' />\n          </div>\n          <p className='text-2xl'>\n            A <span className='font-montserrat font-semibold italic'>Collection</span> is a ...\n          </p>\n          <p className='mt-2 text-sm italic 2xl:mt-4'>\n            A Collection is a set of flows where each flow is a set of API requests chained together, along with each\n            endpoint&lsquo;s authorization type, parameters, headers, request bodies, and settings. A collection enables\n            you to organize your flows into folders or subfolders directly on your local file system. You can then\n            collaborate on these collections with team members using git or any version control system.\n          </p>\n          <div className='flex items-center justify-between gap-6 pt-4 2xl:pt-6'>\n            {collections.length ? (\n              <>\n                <Button\n                  btnType={BUTTON_TYPES.primary}\n                  isDisabled={false}\n                  onClickHandle={() => setNewFlowTestModal(true)}\n                  fullWidth={true}\n                >\n                  Create a Flow\n                </Button>\n                <Button\n                  btnType={BUTTON_TYPES.primary}\n                  isDisabled={false}\n                  onClickHandle={() => {\n                    if (useSettingsStore.getState().genAIUsageDisclaimer === true) {\n                      setGenerateFlowTestModalOpen(true);\n                    } else {\n                      setGenAiUsageDisclaimerModalOpen(true);\n                    }\n                  }}\n                  fullWidth={true}\n                >\n                  Generate a Flow\n                </Button>\n              </>\n            ) : (\n              <>\n                <Button\n                  btnType={BUTTON_TYPES.primary}\n                  isDisabled={false}\n                  onClickHandle={() => setOpenCollectionModal(true)}\n                  fullWidth={true}\n                >\n                  Open a Collection\n                </Button>\n                <Button\n                  btnType={BUTTON_TYPES.primary}\n                  isDisabled={false}\n                  onClickHandle={() => setImportCollectionModal(true)}\n                  fullWidth={true}\n                >\n                  Create a Collection\n                </Button>\n              </>\n            )}\n          </div>\n        </div>\n        {collections.length ? (\n          <>\n            <HorizontalDivider themeColor={'bg-cyan-900'} themeStyles={'opacity-75'} />\n            <div className='text-center'>\n              <div className='flex items-center justify-center'>\n                <Square3Stack3DIcon className='h-24 w-24' />\n              </div>\n              <p className='text-2xl'>\n                An <span className='font-montserrat font-semibold italic'>Environment</span> is a ...\n              </p>\n              <p className='mt-2 text-sm italic 2xl:mt-4'>\n                An environment is a set of one or more variables that you can reference when sending API requests using\n                &#123;&#123; variable &#125;&#125;. When you switch between environments, all of the variables in your\n                requests will use the values from the current environment. This is helpful if you need to use different\n                values in your requests depending on the context, for example, if you are sending a request to a test\n                server or a production server.\n              </p>\n              <div className='flex items-center justify-between gap-6 pt-4 2xl:pt-6'>\n                <Button\n                  btnType={BUTTON_TYPES.primary}\n                  isDisabled={false}\n                  onClickHandle={() => setNewEnvironmentFileModal(true)}\n                  fullWidth={true}\n                >\n                  Create Environment\n                </Button>\n              </div>\n            </div>\n          </>\n        ) : (\n          ''\n        )}\n      </div>\n      <OpenCollectionModal closeFn={() => setOpenCollectionModal(false)} open={openCollectionModal} />\n      <ImportCollectionModal closeFn={() => setImportCollectionModal(false)} open={importCollectionModal} />\n      <NewFlowTestModal closeFn={() => setNewFlowTestModal(false)} open={newFlowTestModal} />\n      <GenerateFlowTestModal closeFn={() => setGenerateFlowTestModalOpen(false)} open={generateFlowTestModalOpen} />\n      <GenAIUsageDisclaimer\n        closeFn={() => setGenAiUsageDisclaimerModalOpen(false)}\n        open={genAiUsageDisclaimerModalOpen}\n        openGenerateFlowTestModal={() => setGenerateFlowTestModalOpen(true)}\n      />\n      <NewEnvironmentFileModal closeFn={() => setNewEnvironmentFileModal(false)} open={newEnvironmentFileModal} />\n    </div>\n  );\n};\n\nexport default EmptyWorkSpaceContent;\n"
  },
  {
    "path": "src/components/molecules/workspace/WorkspaceContent.js",
    "content": "import React from 'react';\nimport Flow, { init } from 'components/molecules/flow';\nimport { useTabStore } from 'stores/TabStore';\nimport useCanvasStore from 'stores/CanvasStore';\nimport TabPanelHeader from '../headers/TabPanelHeader';\nimport { OBJ_TYPES } from 'constants/Common';\nimport Env from '../environment';\nimport useEnvStore from 'stores/EnvStore';\nimport EmptyWorkSpaceContent from './EmptyWorkSpaceContent';\n\nconst WorkspaceContent = () => {\n  const setIntialState = useCanvasStore((state) => state.setIntialState);\n  const setCollectionId = useCanvasStore((state) => state.setCollectionId);\n\n  const setVariables = useEnvStore((state) => state.setVariables);\n\n  const focusTabId = useTabStore((state) => state.focusTabId);\n  const tabs = useTabStore((state) => state.tabs);\n  const focusTab = tabs.find((t) => t.id === focusTabId);\n\n  if (focusTab) {\n    console.log(`Tab changed to: ${focusTabId}`);\n    console.log(focusTab);\n    // perform actions based on the new tabId\n    if (focusTab.type === OBJ_TYPES.flowtest) {\n      const result = init(focusTab.flowDataDraft ? focusTab.flowDataDraft : focusTab.flowData);\n      setIntialState(result);\n      setCollectionId(focusTab.collectionId);\n    } else if (focusTab.type === OBJ_TYPES.environment) {\n      setVariables(focusTab.variablesDraft ? focusTab.variablesDraft : focusTab.variables);\n    }\n  }\n\n  return (\n    <div className='flex h-full flex-col'>\n      <TabPanelHeader />\n      {focusTab ? (\n        focusTab.type === OBJ_TYPES.flowtest ? (\n          <Flow tab={focusTab} collectionId={focusTab.collectionId} />\n        ) : (\n          <Env tab={focusTab} />\n        )\n      ) : (\n        <EmptyWorkSpaceContent />\n      )}\n    </div>\n  );\n};\n\nexport default WorkspaceContent;\n"
  },
  {
    "path": "src/components/organisms/AppNavBar.js",
    "content": "import React from 'react';\nimport { Square3Stack3DIcon, RectangleStackIcon, ClockIcon } from '@heroicons/react/20/solid';\nimport useNavigationStore from 'stores/AppNavBarStore';\nimport { AppNavBarItems, AppNavBarStyles } from 'constants/AppNavBar';\nimport Tippy from '@tippyjs/react';\nimport 'tippy.js/dist/tippy.css';\nimport { Transition } from '@headlessui/react';\n\n// ToDo: May be make this section collapsable and only show Icons when collapsed\nconst AppNavBar = ({ showRightBorder = true }) => {\n  const navigationSelectedValue = useNavigationStore((state) => state.selectedNavVal);\n  const updateNavigationSelectedValue = useNavigationStore((state) => state.setNavState);\n  const isNavBarCollapsed = useNavigationStore((state) => state.collapseNavBar);\n\n  const handleOnClick = (event) => {\n    const dataAttributeValue = event.currentTarget.dataset.navItem;\n    updateNavigationSelectedValue(dataAttributeValue);\n  };\n\n  const selectedNavItemStyles = 'before:bg-cyan-900 bg-background text-cyan-900';\n  const nonSelectedNavItemStyles = 'hover:bg-cyan-900 hover:text-white';\n  const navStyles = 'relative flex h-screen flex-col transition-all delay-150 duration-300';\n  return (\n    <nav\n      className={`${navStyles} ${isNavBarCollapsed ? AppNavBarStyles.collapsedNavBarWidth.tailwindValue.min : AppNavBarStyles.expandedNavBarWidth.tailwindValue.min} ${showRightBorder && !isNavBarCollapsed ? 'border-r border-gray-300' : ''}`}\n    >\n      <button className='relative' onClick={handleOnClick} data-nav-item={AppNavBarItems.collections.value}>\n        <div\n          className={`text-cyan-900 ${\n            navigationSelectedValue === AppNavBarItems.collections.value\n              ? `before:absolute before:left-0 before:top-0 before:h-full before:w-[0.25rem] before:content-[\"\"] ${selectedNavItemStyles}`\n              : nonSelectedNavItemStyles\n          } delay-50 flex w-full flex-col items-center px-2 py-4 text-center transition-all duration-100`}\n        >\n          {isNavBarCollapsed ? (\n            <Tippy content={AppNavBarItems.collections.displayValue} placement='right'>\n              <RectangleStackIcon className='mb-2 h-4 w-4' />\n            </Tippy>\n          ) : (\n            <RectangleStackIcon className='mb-2 h-4 w-4' />\n          )}\n          <Transition\n            show={!isNavBarCollapsed}\n            enter='transition-all ease-in-out duration-500 delay-[200ms]'\n            enterFrom='opacity-0 translate-y-6'\n            enterTo='opacity-100 translate-y-0'\n            leave='transition-all ease-in-out duration-300'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            {AppNavBarItems.collections.displayValue}\n          </Transition>\n        </div>\n      </button>\n      <button className='relative' onClick={handleOnClick} data-nav-item={AppNavBarItems.environments.value}>\n        <div\n          className={`text-cyan-900 ${\n            navigationSelectedValue === AppNavBarItems.environments.value\n              ? `before:absolute before:left-0 before:top-0 before:h-full before:w-[0.25rem] before:content-[\"\"] ${selectedNavItemStyles}`\n              : nonSelectedNavItemStyles\n          } delay-50 flex w-full flex-col items-center px-2 py-4 text-center transition-all duration-100`}\n        >\n          {isNavBarCollapsed ? (\n            <Tippy content={AppNavBarItems.environments.displayValue} placement='right'>\n              <Square3Stack3DIcon className='mb-2 h-4 w-4' />\n            </Tippy>\n          ) : (\n            <Square3Stack3DIcon className='mb-2 h-4 w-4' />\n          )}\n          <Transition\n            show={!isNavBarCollapsed}\n            enter='transition-all ease-in-out duration-500 delay-[200ms]'\n            enterFrom='opacity-0 translate-y-6'\n            enterTo='opacity-100 translate-y-0'\n            leave='transition-all ease-in-out duration-300'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            {AppNavBarItems.environments.displayValue}\n          </Transition>\n        </div>\n      </button>\n      <button className='cursor-not-allowed px-2 py-4 ' data-nav-item={AppNavBarItems.history.value}>\n        <div className='text-gray-300'>\n          {isNavBarCollapsed ? (\n            <div className='transition-all delay-150 duration-300'>\n              <Tippy content={AppNavBarItems.history.displayValue + ': Coming Soon!'} placement='right'>\n                <div className='flex flex-col items-center text-center '>\n                  <ClockIcon className='mb-2 h-4 w-4' />\n                </div>\n              </Tippy>\n            </div>\n          ) : (\n            <Tippy content='Coming Soon!' placement='right'>\n              <div className='flex flex-col items-center text-center '>\n                <ClockIcon className='mb-2 h-4 w-4' />\n              </div>\n            </Tippy>\n          )}\n          <Transition\n            show={!isNavBarCollapsed}\n            enter='transition-all ease-in-out duration-500 delay-[200ms]'\n            enterFrom='opacity-0 translate-y-6'\n            enterTo='opacity-100 translate-y-0'\n            leave='transition-all ease-in-out duration-300'\n            leaveFrom='opacity-100'\n            leaveTo='opacity-0'\n          >\n            {AppNavBarItems.history.displayValue}\n          </Transition>\n        </div>\n      </button>\n    </nav>\n  );\n};\n\nexport default AppNavBar;\n"
  },
  {
    "path": "src/components/organisms/SideBar.js",
    "content": "import React from 'react';\nimport useCollectionStore from 'stores/CollectionStore';\nimport SideBarHeader from 'components/molecules/headers/SideBarHeader';\nimport Empty from 'components/molecules/sidebar/Empty';\nimport Content from 'components/molecules/sidebar/content';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\n\nconst SideBar = () => {\n  const collections = useCollectionStore((state) => state.collections);\n  return (\n    <div className='flex-auto overflow-auto'>\n      <SideBarHeader />\n      <HorizontalDivider />\n      {/* ToDo: Remove the check for collections here as it is not required with UX */}\n      <>{collections.length != 0 ? <Content collections={collections} /> : <Empty />}</>\n    </div>\n  );\n};\n\nexport default SideBar;\n"
  },
  {
    "path": "src/components/organisms/workspace/Workspace.js",
    "content": "import React from 'react';\nimport WorkspaceHeader from 'components/molecules/headers/WorkspaceHeader';\nimport WorkspaceContent from 'components/molecules/workspace/WorkspaceContent';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\n\nconst Workspace = () => {\n  return (\n    <div className='flex flex-col h-full'>\n      <WorkspaceHeader />\n      <HorizontalDivider />\n      <WorkspaceContent />\n    </div>\n  );\n};\n\nexport default Workspace;\n"
  },
  {
    "path": "src/components/pages/Home.js",
    "content": "import React from 'react';\nimport SplitPane from '../layouts/SplitPane';\nimport MainHeader from '../molecules/headers/MainHeader';\nimport MainFooter from '../molecules/footers/MainFooter';\nimport registerMainEventHandlers from 'ipc/collection';\nimport { ToastContainer, toast } from 'react-toastify';\nimport 'react-toastify/dist/ReactToastify.css';\nimport useCommonStore from 'stores/CommonStore';\nimport LoadingSpinner from 'components/atoms/common/LoadingSpinner';\nimport HorizontalDivider from 'components/atoms/common/HorizontalDivider';\nimport WithoutSplitPane from 'components/layouts/WithoutSplitPane';\nimport useCollectionStore from 'stores/CollectionStore';\nimport registerSettingsEventHandlers from 'ipc/settings';\nimport useTelemetry from 'utils/useTelemetry';\n\nconst Home = () => {\n  const collections = useCollectionStore((state) => state.collections);\n  const showLoader = useCommonStore((state) => state.showLoader);\n  registerMainEventHandlers();\n  registerSettingsEventHandlers();\n  useTelemetry();\n  return (\n    <div className='relative flex flex-col h-full font-openSans text-cyan-900'>\n      <MainHeader />\n      <HorizontalDivider />\n      {/* For some reason with this condition in SplitPane component, drag feature was not getting enabled thus putting this here \n        and in future we will remove SplitPane dependency from WithoutSplitPane component but it will require significance effort\n      */}\n      {collections.length ? <SplitPane /> : <WithoutSplitPane />}\n      <HorizontalDivider />\n      <MainFooter />\n      <ToastContainer position='bottom-left' />\n      {showLoader ? <LoadingSpinner spinnerColor={'text-cyan-950'} /> : ''}\n    </div>\n  );\n};\n\nexport default Home;\n"
  },
  {
    "path": "src/constants/AppNavBar.js",
    "content": "export const AppNavBarItems = {\n  // default value\n  collections: {\n    displayValue: 'Collections',\n    value: 'COLLECTIONS',\n    active: true, // by default active state\n    disable: false,\n  },\n  environments: {\n    displayValue: 'Environments',\n    value: 'ENVIRONMENTS',\n    active: false,\n    disable: false,\n  },\n  history: {\n    displayValue: 'History',\n    value: 'HISTORY',\n    active: false,\n    disable: true,\n  },\n};\n\nexport const AppNavBarStyles = {\n  collapsedNavBarWidth: {\n    absolute: 56,\n    pixelInString: '56px',\n    tailwindValue: {\n      default: 'w-14',\n      min: 'min-w-14',\n    },\n  },\n  expandedNavBarWidth: {\n    absolute: 12,\n    pixelInString: '12px',\n    tailwindValue: {\n      default: 'w-28',\n      min: 'min-w-28',\n    },\n  },\n};\n"
  },
  {
    "path": "src/constants/Common.js",
    "content": "export const FLOW_FILE_SUFFIX_REGEX = /^.+\\.flow$/gm; // regex to check the file extension of flow files\n\nexport const CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ = {\n  value: 'choose_operator',\n  displayValue: 'Choose Operator',\n};\n\nexport const INPUT_DEFAULT_TYPE = 'Text';\n\nexport const BUTTON_TYPES = {\n  primary: 'primary',\n  secondary: 'secondary',\n  tertiary: 'tertiary',\n  minimal: 'minimal',\n  disabled: 'disabled',\n};\n\nexport const BUTTON_INTENT_TYPES = {\n  info: 'info',\n  success: 'success',\n  warning: 'warning',\n  error: 'error',\n};\n\nexport const OBJ_TYPES = {\n  collection: 'collection',\n  flowtest: 'flowtest',\n  folder: 'folder',\n  environment: 'environment',\n};\n\nexport const GENAI_MODELS = {\n  openai: 'OPENAI',\n  bedrock_claude: 'BEDROCK_CLAUDE',\n  gemini: 'GEMINI',\n};\n"
  },
  {
    "path": "src/constants/ImportCollectionTypes.js",
    "content": "const ImportCollectionTypes = {\n  YAML: 'yaml',\n  OPEN_API: 'open_api',\n  POSTMAN: 'postman',\n};\n\nexport default ImportCollectionTypes;\n"
  },
  {
    "path": "src/constants/ModalNames.js",
    "content": "export const ModalNames = {\n  IMPORT_COLLECTION_MODAL: 'import-collection-modal',\n  CREATE_NEW_COLLECTION_MODAL: 'create-new-collection-modal',\n};\n"
  },
  {
    "path": "src/constants/WorkspaceDirectory.js",
    "content": "export const DirectoryOptionsActions = {\n  addNewFolder: {\n    displayValue: 'New Folder',\n    value: 'new-folder',\n    dataSetValue: 'new-folder',\n  },\n  delete: {\n    displayValue: 'Delete',\n    value: 'delete',\n    dataSetValue: 'delete',\n  },\n  remove: {\n    displayValue: 'Remove',\n    value: 'remove',\n    dataSetValue: 'remove',\n  },\n  addNewFlow: {\n    displayValue: 'New Flow',\n    value: 'new-flow',\n    dataSetValue: 'new-flow',\n  },\n  cloneFlow: {\n    displayValue: 'Clone Flow',\n    value: 'clone-flow',\n    dataSetValue: 'clone-flow',\n  },\n  addNewEnvironment: {\n    displayValue: 'New Environment',\n    value: 'new-environment',\n    dataSetValue: 'new-environment',\n  },\n};\n"
  },
  {
    "path": "src/constants/sidebar/Environnments.js",
    "content": "export const OptionsMenuActions = {\n  addNewEnvironment: {\n    displayValue: 'New Environment',\n    value: 'new-environment',\n    dataSetValue: 'new-environment',\n  },\n};\n"
  },
  {
    "path": "src/index.css",
    "content": "@import 'tailwindcss/base';\n@import 'tailwindcss/components';\n@import 'tailwindcss/utilities';\n@import url('https://fonts.googleapis.com/css2?family=Antic+Didone&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');\n\n#root {\n  width: 100vw;\n  height: 100vh;\n}\n\ndetails summary::-webkit-details-marker {\n  display: none;\n}\n\n/* This is an important override since we do not wants show anchor tag instead we wants to show another icon */\n.menu :where(li > .menu-dropdown-toggle):after,\n.menu :where(li > details > summary):after {\n  content: none;\n}\n\n/* Side Sheet component related CSS overrides */\n.side-sheet-overlay.slide-pane__overlay.overlay-after-open {\n  background-color: rgba(67, 90, 111, 0.6);\n}\n.side-sheet-overlay .side-sheet.slide-pane {\n  box-shadow: none;\n}\n.side-sheet-overlay .slide-pane__content {\n  background: #fafbff;\n}\n.side-sheet-overlay .slide-pane__header {\n  background: #fff;\n  border-bottom: 1px solid #edeff5;\n  padding: 3rem 2rem;\n  flex: 0 auto;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__close {\n  margin-left: 0;\n  padding: 0;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__title-wrapper {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  margin-left: 1.5rem;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__title-wrapper .slide-pane__title {\n  color: #101840;\n  font-size: 1.5rem;\n  line-height: 24px;\n  font-weight: 500;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__title-wrapper .slide-pane__title .pane_title_text {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__title-wrapper .slide-pane__subtitle {\n  color: #696f8c;\n  line-height: 24px;\n  font-size: 1.25rem;\n  margin-top: 0;\n}\n.side-sheet-overlay .slide-pane__header .slide-pane__title-wrapper .slide-pane__subtitle .pane_subtitle_text {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.side-sheet-overlay .slide-pane__content {\n  padding: 1rem 2rem;\n}\n\n/* React flow related CSS */\n.react-flow .react-flow__handle {\n  width: 14px;\n  height: 30px;\n  border-radius: 3px;\n  background-color: #164e63;\n}\n\n.react-flow .react-flow__handle-left {\n  left: -6px;\n}\n\n.react-flow .react-flow__handle-right {\n  right: -6px;\n}\n\n.react-flow__controls {\n  box-shadow: none;\n}\n\n.react-flow__controls .react-flow__controls-button {\n  min-width: 1.5rem;\n  padding: 0.75rem 0.5rem;\n  border-top: 1px solid #164e63;\n  border-bottom: 1px solid #164e63;\n  border-right: 1px solid #164e63;\n  background-color: #f1f5f9;\n}\n\n.react-flow__controls .react-flow__controls-button:first-child {\n  border-top-left-radius: 0.25rem;\n  border-bottom-left-radius: 0.25rem;\n  border-left: 1px solid #164e63;\n}\n\n.react-flow__controls .react-flow__controls-button:last-child {\n  border-top-right-radius: 0.25rem;\n  border-bottom-right-radius: 0.25rem;\n  border-left: 0;\n  border-right: 1px solid #164e63;\n}\n\n.react-flow__controls .react-flow__controls-button:hover {\n  background-color: #e2e8f0;\n}\n\n/* Not working as now so ToDo to check the styles for react-flow__edge */\n.react-flow .react-flow__edge {\n  color: #164e63;\n  background-color: #164e63;\n}\n\n/* Toastify */\n/* https://github.com/fkhadra/react-toastify/issues/201 */\n.Toastify__toast-body {\n  white-space: pre-line;\n}\n\n/* Editor related styles */\n/* .cm-editor {\n  max-width: 384px;\n  max-height: 384px;\n  overflow: auto !important;\n}\n.cm-scroller {\n  overflow: auto;\n} */\n\n/* Overriding style for react-json-view-lite */\n.json-view-container > div {\n  background: transparent !important;\n}\n\n.flow-logs-menu {\n  margin-inline-start: 1.25rem;\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"
  },
  {
    "path": "src/ipc/collection.js",
    "content": "import useCollectionStore from 'stores/CollectionStore';\nimport { useEffect } from 'react';\n\nconst registerMainEventHandlers = () => {\n  const _createCollection = useCollectionStore((state) => state.createCollection);\n  const _deleteCollection = useCollectionStore((state) => state.deleteCollection);\n  const _createFolder = useCollectionStore((state) => state.createFolder);\n  const _deleteFolder = useCollectionStore((state) => state.deleteFolder);\n  const _addOrUpdateEnvFile = useCollectionStore((state) => state.addOrUpdateEnvFile);\n  const _deleteEnvFile = useCollectionStore((state) => state.deleteEnvFile);\n  const _addOrUpdateDotEnvVariables = useCollectionStore((state) => state.addOrUpdateDotEnvVariables);\n  const _createFlowTest = useCollectionStore((state) => state.createFlowTest);\n  const _readFlowTest = useCollectionStore((state) => state.readFlowTest);\n  const _updateFlowTest = useCollectionStore((state) => state.updateFlowTest);\n  const _deleteFlowTest = useCollectionStore((state) => state.deleteFlowTest);\n\n  useEffect(() => {\n    const { ipcRenderer } = window;\n\n    ipcRenderer.on('main:collection-created', (id, name, pathname, nodes) => {\n      _createCollection(id, name, pathname, nodes);\n    });\n\n    ipcRenderer.on('main:collection-deleted', (collectionId) => {\n      _deleteCollection(collectionId);\n    });\n\n    ipcRenderer.on('main:add-directory', (directory, collectionId, subDirsFromRoot, PATH_SEPARATOR) => {\n      _createFolder(directory, collectionId, subDirsFromRoot, PATH_SEPARATOR);\n    });\n\n    ipcRenderer.on('main:delete-directory', (directory, collectionId) => {\n      _deleteFolder(directory, collectionId);\n    });\n\n    ipcRenderer.on('main:addOrUpdate-environment', (file, collectionId) => {\n      _addOrUpdateEnvFile(file, collectionId);\n    });\n\n    ipcRenderer.on('main:delete-environment', (file, collectionId) => {\n      _deleteEnvFile(file, collectionId);\n    });\n\n    ipcRenderer.on('main:addOrUpdate-dotEnvironment', (variables, collectionId) => {\n      _addOrUpdateDotEnvVariables(variables, collectionId);\n    });\n\n    ipcRenderer.on('main:create-flowtest', (file, collectionId) => {\n      _createFlowTest(file, collectionId);\n    });\n\n    ipcRenderer.on('main:read-flowtest', (pathname, collectionId, flowData) => {\n      _readFlowTest(pathname, collectionId, flowData);\n    });\n\n    ipcRenderer.on('main:update-flowtest', (file, collectionId) => {\n      _updateFlowTest(file, collectionId);\n    });\n\n    ipcRenderer.on('main:delete-flowtest', (file, collectionId) => {\n      _deleteFlowTest(file, collectionId);\n    });\n\n    ipcRenderer.invoke('renderer:browser-window-ready');\n  }, []);\n};\n\nexport default registerMainEventHandlers;\n"
  },
  {
    "path": "src/ipc/settings.js",
    "content": "import { useEffect } from 'react';\nimport useSettingsStore from 'stores/SettingsStore';\n\nconst registerSettingsEventHandlers = () => {\n  const _addLogSyncConfig = useSettingsStore((state) => state.addLogSyncConfig);\n  const _addGenAIUsageDisclaimer = useSettingsStore((state) => state.addGenAIUsageDisclaimer);\n  const _updateAppVersion = useSettingsStore((state) => state.updateAppVersion);\n\n  useEffect(() => {\n    const { ipcRenderer } = window;\n\n    ipcRenderer.on('main:saved-settings', (savedSettings) => {\n      if (savedSettings.logSyncConfig) {\n        const config = savedSettings.logSyncConfig;\n        _addLogSyncConfig(config.enabled || false, config.hostUrl || '', config.accessId || '', config.accessKey || '');\n      }\n\n      if (savedSettings.genAIUsageDisclaimer) {\n        _addGenAIUsageDisclaimer(savedSettings.genAIUsageDisclaimer);\n      }\n    });\n\n    ipcRenderer.on('main:app-version', (version) => {\n      _updateAppVersion(version);\n    });\n\n    ipcRenderer.invoke('renderer:settings-window-ready');\n  }, []);\n};\n\nexport default registerSettingsEventHandlers;\n"
  },
  {
    "path": "src/reportWebVitals.js",
    "content": "const reportWebVitals = (onPerfEntry) => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "src/routes/Main.js",
    "content": "import React from 'react';\nimport { Outlet } from 'react-router';\nimport Home from '../components/pages/Home';\n\nconst Main = {\n  path: '/',\n  element: <Outlet />,\n  children: [\n    {\n      path: '/',\n      element: <Home />,\n    },\n  ],\n};\n\nexport default Main;\n"
  },
  {
    "path": "src/routes/index.js",
    "content": "import { useRoutes } from 'react-router';\nimport Main from 'routes/Main';\n\nexport default function Routes() {\n  return useRoutes([Main]);\n}\n"
  },
  {
    "path": "src/service/collection.js",
    "content": "import useCollectionStore from '../stores/CollectionStore';\nimport { v4 as uuidv4 } from 'uuid';\nimport { findItemInCollectionByPathname } from 'stores/utils';\nimport { useEventStore } from 'stores/EventListenerStore';\nimport { OBJ_TYPES } from 'constants/Common';\nimport { toast } from 'react-toastify';\nimport { useTabStore } from 'stores/TabStore';\nimport useCanvasStore from 'stores/CanvasStore';\nimport { initFlowData } from 'components/molecules/flow/utils';\nimport { cloneDeep } from 'lodash';\nimport useSettingsStore from 'stores/SettingsStore';\n\nexport const createCollection = (openAPISpecFilePath, collectionFolderPath) => {\n  const { ipcRenderer } = window;\n\n  return new Promise((resolve, reject) => {\n    ipcRenderer\n      .invoke('renderer:create-collection', openAPISpecFilePath, collectionFolderPath)\n      .then(resolve)\n      .catch(reject);\n  });\n};\n\nexport const openCollection = (openAPISpecFilePath, collectionFolderPath) => {\n  try {\n    const collection = useCollectionStore.getState().collections.find((c) => c.pathname === collectionFolderPath);\n    if (collection) {\n      return Promise.reject(new Error(`A collection with path: ${collectionFolderPath} already exists`));\n    } else {\n      const { ipcRenderer } = window;\n\n      return new Promise((resolve, reject) => {\n        ipcRenderer\n          .invoke('renderer:open-collection', openAPISpecFilePath, collectionFolderPath)\n          .then(resolve)\n          .catch(reject);\n      });\n    }\n  } catch (error) {\n    console.log(`Error opening collection: ${error}`);\n    //toast.error('Error opening collection');\n    return Promise.reject(new Error('Error opening collection'));\n  }\n};\n\nexport const deleteCollection = (collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      return new Promise((resolve, reject) => {\n        ipcRenderer.invoke('renderer:delete-collection', collection).then(resolve).catch(reject);\n      });\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error deleting collection: ${error}`);\n    //toast.error('Error deleting collection');\n    return Promise.reject(new Error('Error deleting collection'));\n  }\n};\n\nexport const createFolder = (folderName, folderPath, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const folderPathItem = findItemInCollectionByPathname(collection, folderPath);\n      const sameFolderExists = folderPathItem.items.find((i) => i.type === OBJ_TYPES.folder && i.name === folderName);\n      if (sameFolderExists) {\n        return Promise.reject(new Error('A folder with the same name already exists'));\n      } else {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:create-folder', folderName, folderPath).then(resolve).catch(reject);\n        });\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error creating new folder: ${error}`);\n    //toast.error('Error creating new folder');\n    return Promise.reject(new Error('Error creating new folder'));\n  }\n};\n\nexport const deleteFolder = (folderPath, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const folderPathItem = findItemInCollectionByPathname(collection, folderPath);\n      if (folderPathItem) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:delete-folder', folderPath).then(resolve).catch(reject);\n        });\n      } else {\n        return Promise.reject(new Error('Folder with the given path does not exist'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error deleting folder: ${error}`);\n    //toast.error('Error deleting folder');\n    return Promise.reject(new Error('Error deleting folder'));\n  }\n};\n\nexport const createEnvironmentFile = (name, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      const existingEnv = collection.environments.find((e) => e.name === `${name}.env`);\n      if (existingEnv) {\n        return Promise.reject(new Error('An environment with the same name already exists'));\n      } else {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:create-environment', collection.pathname, name).then(resolve).catch(reject);\n          useEventStore.getState().addEvent({\n            id: uuidv4(),\n            type: 'OPEN_NEW_ENVIRONMENT',\n            collectionId,\n            name: `${name}.env`,\n          });\n        });\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    //toast.error('Error creating new environment');\n    return Promise.reject(new Error('Error creating new environment'));\n  }\n};\n\nexport const readEnvironmentFile = (name, collectionId) => {\n  try {\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      const existingEnv = collection.environments.find((e) => e.name === name);\n      if (existingEnv) {\n        useTabStore.getState().addEnvTab(existingEnv, collectionId);\n        return;\n      } else {\n        throw new Error('An environment with the name does not exists');\n      }\n    } else {\n      throw new Error('Collection not found');\n    }\n  } catch (error) {\n    //toast.error(`Error reading environment: ${name}`);\n    return Promise.reject(new Error('Error reading environment'));\n  }\n};\n\nexport const updateEnvironmentFile = (name, collectionId, variables) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      const existingEnv = collection.environments.find((e) => e.name === name);\n      if (existingEnv) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer\n            .invoke('renderer:update-environment', collection.pathname, name, variables)\n            .then(resolve)\n            .catch(reject);\n        });\n      } else {\n        return Promise.reject(new Error('An environment with the name does not exists'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error updating environment: ${error}`);\n    //toast.error('Error updating environment');\n    return Promise.reject(new Error('Error updating environment'));\n  }\n};\n\nexport const deleteEnvironmentFile = (name, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      const existingEnv = collection.environments.find((e) => e.name === name);\n      if (existingEnv) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:delete-environment', collection.pathname, name).then(resolve).catch(reject);\n        });\n      } else {\n        return Promise.reject(new Error('An environment with the name does not exists'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    //toast.error('Error deleting environment');\n    return Promise.reject(new Error('Error deleting environment'));\n  }\n};\n\nexport const addOrUpdateDotEnvironmentFile = (collectionId, variables) => {\n  const { ipcRenderer } = window;\n\n  const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n\n  if (collection) {\n    const env = Object.entries(variables)\n      .map(([key, value]) => `${key}: \"${value}\"`)\n      .join('\\n');\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer.invoke('renderer:addOrUpdate-dotEnvironment', collection.pathname, env).then(resolve).catch(reject);\n    });\n  } else {\n    return Promise.reject(new Error('Collection not found'));\n  }\n};\n\nexport const createFlowTest = (name, folderPath, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const folderPathItem = findItemInCollectionByPathname(collection, folderPath);\n      const sameFlowTestExists = folderPathItem.items.find(\n        (i) => i.type === OBJ_TYPES.flowtest && i.name === `${name}.flow`,\n      );\n      if (sameFlowTestExists) {\n        return Promise.reject(new Error('A flowtest with the same name already exists'));\n      } else {\n        return new Promise((resolve, reject) => {\n          ipcRenderer\n            .invoke('renderer:create-flowtest', name, folderPath, cloneDeep(initFlowData))\n            .then(resolve)\n            .catch(reject);\n          useEventStore.getState().addEvent({\n            id: uuidv4(),\n            type: 'OPEN_NEW_FLOWTEST',\n            collectionId,\n            name: `${name}.flow`,\n            path: folderPath,\n          });\n        });\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error creating new flowtest: ${error}`);\n    //toast.error('Error creating new flowtest');\n    return Promise.reject(new Error('Error creating new flowtest'));\n  }\n};\n\nexport const cloneFlowTest = (name, flowTestPath, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const flowtest = findItemInCollectionByPathname(collection, flowTestPath);\n      if (flowtest) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:clone-flowtest', name, flowTestPath).then(resolve).catch(reject);\n          useEventStore.getState().addEvent({\n            id: uuidv4(),\n            type: 'OPEN_NEW_FLOWTEST',\n            collectionId,\n            name: `${name}.flow`,\n            path: ipcRenderer.dirname(flowTestPath),\n          });\n        });\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error cloning flowtest: ${error}`);\n    //toast.error('Error creating new flowtest');\n    return Promise.reject(new Error('Error cloning flowtest'));\n  }\n};\n\nexport const readFlowTest = (pathname, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const flowtest = findItemInCollectionByPathname(collection, pathname);\n      if (flowtest) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:read-flowtest', pathname, collectionId).then(resolve).catch(reject);\n          useEventStore.getState().addEvent({\n            id: uuidv4(),\n            type: 'OPEN_SAVED_FLOWTEST',\n            collectionId,\n            name: flowtest.name,\n            pathname: flowtest.pathname,\n          });\n        });\n      } else {\n        return Promise.reject(new Error('A flowtest with this path does not exist'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error reading flowtest: ${error}`);\n    //toast.error('Error reading flowtest');\n    return Promise.reject(new Error('Error reading flowtest'));\n  }\n};\n\nexport const readFlowTestSync = (relativePath) => {\n  try {\n    if (relativePath.trim() != '') {\n      const { ipcRenderer } = window;\n\n      // assumes that this function is triggered from graph run\n      const collectionId = useCanvasStore.getState().collectionId;\n      const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n      if (collection) {\n        return new Promise((resolve, reject) => {\n          return ipcRenderer\n            .invoke('renderer:read-flowtest-sync', ipcRenderer.join(collection.pathname, relativePath))\n            .then(resolve)\n            .catch(reject);\n        });\n      }\n    }\n  } catch (error) {\n    console.log(`Error reading flowtest: ${error}`);\n    return Promise.reject(new Error('Error reading flowtest'));\n  }\n};\n\n// rename flowtest\n// tab id is flowtest id, so when rename event happens\nexport const updateFlowTest = (pathname, flowData, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const flowtest = findItemInCollectionByPathname(collection, pathname);\n      if (flowtest) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:update-flowtest', pathname, flowData).then(resolve).catch(reject);\n        });\n      } else {\n        return Promise.reject(new Error('A flowtest with this path does not exist'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error updating flowtest: ${error}`);\n    //toast.error('Error updating flowtest');\n    return Promise.reject(new Error('Error updating flowtest'));\n  }\n};\n\nexport const deleteFlowTest = (pathname, collectionId) => {\n  try {\n    const { ipcRenderer } = window;\n\n    const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);\n    if (collection) {\n      const flowtest = findItemInCollectionByPathname(collection, pathname);\n      if (flowtest) {\n        return new Promise((resolve, reject) => {\n          ipcRenderer.invoke('renderer:delete-flowtest', pathname).then(resolve).catch(reject);\n        });\n      } else {\n        return Promise.reject(new Error('A flowtest with this path does not exist'));\n      }\n    } else {\n      return Promise.reject(new Error('Collection not found'));\n    }\n  } catch (error) {\n    console.log(`Error deleting flowtest: ${error}`);\n    //toast.error('Error deleting flowtest');\n    return Promise.reject(new Error('Error deleting flowtest'));\n  }\n};\n\nexport const uploadGraphRunLogs = async (name, status, time, logs) => {\n  try {\n    const { ipcRenderer } = window;\n    const logSyncConfig = useSettingsStore.getState().logSyncConfig;\n\n    if (logSyncConfig.enabled) {\n      return await ipcRenderer.invoke('renderer:upload-logs', name, logSyncConfig, status, time, logs);\n    } else {\n      return {\n        upload: 'disabled',\n        message: 'Enable flow scans to get more value out of your APIs',\n      };\n    }\n  } catch (error) {\n    return {\n      upload: 'fail',\n      message: 'Unable to upload flow scan',\n    };\n  }\n};\n"
  },
  {
    "path": "src/service/settings.js",
    "content": "export const addLogSyncConfig = (enabled, hostUrl, accessId, accessKey) => {\n  try {\n    const { ipcRenderer } = window;\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer\n        .invoke('renderer:add-logsyncconfig', { enabled, hostUrl, accessId, accessKey })\n        .then(resolve)\n        .catch(reject);\n    });\n  } catch (error) {\n    return Promise.reject(new Error('Unable to update config'));\n  }\n};\n\nexport const addGenAIUsageDisclaimer = (accepted) => {\n  try {\n    const { ipcRenderer } = window;\n\n    return new Promise((resolve, reject) => {\n      ipcRenderer.invoke('renderer:add-genAIUsageDisclaimer', accepted).then(resolve).catch(reject);\n    });\n  } catch (error) {\n    return Promise.reject(new Error('Unable to acknowledge'));\n  }\n};\n"
  },
  {
    "path": "src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\n"
  },
  {
    "path": "src/stores/AppNavBarStore.js",
    "content": "import { create } from 'zustand';\nimport { AppNavBarItems } from 'constants/AppNavBar';\n\nconst useNavigationStore = create((set) => ({\n  selectedNavVal: AppNavBarItems.collections.value, // default value\n  collapseNavBar: false,\n  setNavState: (selectedVal) => set(() => ({ selectedNavVal: selectedVal })),\n  setNavCollapseState: (collapseNavBarValue) => set(() => ({ collapseNavBar: collapseNavBarValue })),\n}));\n\nexport default useNavigationStore;\n"
  },
  {
    "path": "src/stores/CanvasStore.js",
    "content": "import { create } from 'zustand';\nimport { addEdge, applyNodeChanges, applyEdgeChanges } from 'reactflow';\nimport { useTabStore } from './TabStore';\nimport { getDefaultValue } from 'utils/common';\n\n// this is our useStore hook that we can use in our components to get parts of the store and call actions\nconst useCanvasStore = create((set, get) => ({\n  nodes: [],\n  edges: [],\n  collectionId: '',\n  timeout: '60000',\n  viewport: { x: 0, y: 0, zoom: 1 },\n  onNodesChange: (changes) => {\n    set({\n      nodes: applyNodeChanges(changes, get().nodes),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  onEdgesChange: (changes) => {\n    set({\n      edges: applyEdgeChanges(changes, get().edges),\n    });\n    useTabStore.getState().updateFlowTestEdges(useTabStore.getState().focusTabId, get().edges);\n  },\n  onConnect: (connection) => {\n    const newEdge = {\n      ...connection,\n      type: 'buttonedge',\n    };\n    set({\n      edges: addEdge(newEdge, get().edges),\n    });\n    useTabStore.getState().updateFlowTestEdges(useTabStore.getState().focusTabId, get().edges);\n  },\n  setIntialState: ({ nodes, edges, viewport }) => {\n    set((state) => ({ ...state, ...{ nodes, edges, viewport } }));\n  },\n  setNodes: (nodes) => {\n    set({ nodes });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setEdges: (edges) => {\n    set({ edges });\n    useTabStore.getState().updateFlowTestEdges(useTabStore.getState().focusTabId, get().edges);\n  },\n  setCollectionId: (collectionId) => {\n    set({ collectionId });\n  },\n  setTimeout: (timeout) => {\n    set({ timeout });\n  },\n  setViewport: (viewport) => {\n    set({ viewport });\n    useTabStore.getState().updateFlowTestViewport(useTabStore.getState().focusTabId, get().viewport);\n  },\n  setAuthNodeType: (nodeId, authType) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          if (authType == 'no-auth') {\n            return {\n              ...node,\n              data: {\n                type: authType,\n              },\n            };\n          } else if (authType == 'basic-auth') {\n            return {\n              ...node,\n              data: {\n                type: authType,\n                username: '',\n                password: '',\n              },\n            };\n          } else if (authType == 'bearer-token') {\n            return {\n              ...node,\n              data: {\n                type: authType,\n                token: '',\n              },\n            };\n          }\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setBasicAuthValues: (nodeId, attribute, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              [attribute]: value,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setBearerTokenValue: (nodeId, token) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              token,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setRequestNodeType: (nodeId, requestType) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              requestType,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setRequestNodeUrl: (nodeId, url) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              url,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeAddPreRequestVar: (nodeId, name, type) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const newId = name;\n          const newVar = {\n            type: type,\n            value: getDefaultValue(type),\n          };\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              preReqVars: {\n                ...node.data.preReqVars,\n                [newId]: newVar,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeDeletePreRequestVar: (nodeId, id) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const { [id]: _, ...newVariables } = node.data.preReqVars;\n\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              preReqVars: newVariables,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeChangePreRequestVar: (nodeId, id, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const updateVar = {\n            type: node.data.preReqVars[id].type,\n            value,\n          };\n\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              preReqVars: {\n                ...node.data.preReqVars,\n                [id]: updateVar,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeAddPostResponseVar: (nodeId, name, type) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const newId = name;\n          const newVar = {\n            type: type,\n            value: getDefaultValue(type),\n          };\n\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              postRespVars: {\n                ...node.data.postRespVars,\n                [newId]: newVar,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeDeletePostResponseVar: (nodeId, id) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const { [id]: _, ...newVariables } = node.data.postRespVars;\n\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              postRespVars: newVariables,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  requestNodeChangePostResponseVar: (nodeId, id, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          const updateVar = {\n            type: node.data.postRespVars[id].type,\n            value,\n          };\n\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              postRespVars: {\n                ...node.data.postRespVars,\n                [id]: updateVar,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setRequestNodeBody: (nodeId, type, data) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          if (type === 'None') {\n            const { ['requestBody']: _, ...data } = node.data;\n            return {\n              ...node,\n              data,\n            };\n          } else if (type === 'raw-json') {\n            return {\n              ...node,\n              data: {\n                ...node.data,\n                requestBody: {\n                  type,\n                  body: data,\n                },\n              },\n            };\n          } else if (type === 'form-data') {\n            return {\n              ...node,\n              data: {\n                ...node.data,\n                requestBody: {\n                  type,\n                  body: data,\n                },\n              },\n            };\n          }\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setRequestNodeHeaders: (nodeId, headers) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          if (Object.entries(headers).length === 0) {\n            const { ['headers']: _, ...data } = node.data;\n            return {\n              ...node,\n              data,\n            };\n          } else {\n            return {\n              ...node,\n              data: {\n                ...node.data,\n                headers,\n              },\n            };\n          }\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setAssertNodeVariable: (nodeId, name, type, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variables: {\n                ...node.data.variables,\n                [name]: {\n                  type,\n                  value,\n                },\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setAssertNodeOperator: (nodeId, operator) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              operator,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setDelayNodeValue: (nodeId, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              delay: value,\n            },\n          };\n        }\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setFlowForComplexNode: (nodeId, relativePath) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              relativePath,\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setVariableNodeName: (nodeId, name) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variable: {\n                ...node.data?.variable,\n                name,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setVariableNodeType: (nodeId, type) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variable: {\n                ...node.data?.variable,\n                type,\n                value: type === 'Expression' ? {} : getDefaultValue(type),\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setVariableNodeExpressionsVariable: (nodeId, name, type, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variable: {\n                ...node.data.variable,\n                value: {\n                  ...node.data.variable.value,\n                  variables: {\n                    ...node.data.variable.value?.variables,\n                    [name]: {\n                      type,\n                      value,\n                    },\n                  },\n                },\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  setVariableNodeExpressionOperator: (nodeId, operator) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variable: {\n                ...node.data.variable,\n                value: {\n                  ...node.data.variable.value,\n                  operator,\n                },\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n  variableNodeChangeVar: (nodeId, value) => {\n    set({\n      nodes: get().nodes.map((node) => {\n        if (node.id === nodeId) {\n          // it's important to create a new object here, to inform React Flow about the cahnges\n          return {\n            ...node,\n            data: {\n              ...node.data,\n              variable: {\n                ...node.data.variable,\n                value,\n              },\n            },\n          };\n        }\n\n        return node;\n      }),\n    });\n    useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);\n  },\n}));\n\nexport default useCanvasStore;\n"
  },
  {
    "path": "src/stores/CollectionStore.js",
    "content": "import { create } from 'zustand';\nimport { produce } from 'immer';\nimport { v4 as uuidv4 } from 'uuid';\nimport {\n  findItemInCollectionTree,\n  deleteItemInCollectionByPathname,\n  findItemInCollectionByPathname,\n  flattenItems,\n} from './utils.js';\nimport { useEventStore } from './EventListenerStore.js';\nimport { useTabStore } from './TabStore.js';\nimport { OBJ_TYPES } from 'constants/Common.js';\nimport { cloneDeep } from 'lodash';\n\nconst useCollectionStore = create((set, get) => ({\n  collections: [],\n  createCollection: (id, name, pathname, nodes) => {\n    const collectionObj = {\n      version: '1',\n      id: id,\n      type: OBJ_TYPES.collection,\n      name: name,\n      pathname: pathname,\n      collapsed: true,\n      nodes: nodes,\n      items: [],\n      environments: [],\n      envCollapsed: true,\n    };\n    if (!get().collections.find((c) => c.pathname === pathname)) {\n      set((state) => ({ collections: [...state.collections, collectionObj] }));\n      console.log(`Collection added => id: ${id}, name: ${name}, pathname: ${pathname}`);\n    }\n  },\n  deleteCollection(collectionId) {\n    set((state) => ({ collections: state.collections.filter((c) => c.id != collectionId) }));\n    console.log(`Collection removed: ${collectionId}`);\n\n    // check if there any open tabs, if yes close them\n    useTabStore.getState().closeCollectionTabs(collectionId);\n  },\n  createFolder: (directory, collectionId, subDirsFromRoot, PATH_SEPARATOR) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          let currentPath = collection.pathname;\n          let currentSubItems = collection.items;\n          for (const directoryName of subDirsFromRoot) {\n            let childItem = currentSubItems.find((f) => f.type === OBJ_TYPES.folder && f.name === directoryName);\n            if (!childItem) {\n              childItem = {\n                id: uuidv4(),\n                pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`,\n                name: directoryName,\n                type: OBJ_TYPES.folder,\n                collapsed: true,\n                items: [],\n              };\n              currentSubItems.push(childItem);\n            }\n\n            currentPath = `${currentPath}${PATH_SEPARATOR}${directoryName}`;\n            currentSubItems = childItem.items;\n          }\n          //console.log(`Collection folder created: ${JSON.stringify(get().collections)}`);\n        }\n      }),\n    );\n  },\n  deleteFolder: (directory, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          // if it's the collection itself\n          if (collection.pathname === directory.pathname && collection.name === directory.name) {\n            state.collections = state.collections.filter((c) => c.id != collectionId);\n            console.log(`Collection removed: ${collectionId}`);\n\n            // check if there any open tabs, if yes close them\n            useTabStore.getState().closeCollectionTabs(collectionId);\n          } else {\n            const item = findItemInCollectionTree(directory, collection);\n\n            if (item) {\n              const flowTestIds = flattenItems(item.items).map((i) => {\n                if (i.type !== OBJ_TYPES.folder) {\n                  i.id;\n                }\n              });\n\n              deleteItemInCollectionByPathname(item.pathname, collection);\n              //console.log(`Collection folder deleted: ${JSON.stringify(get().collections)}`);\n\n              // check if there any open tabs, if yes close them\n              useTabStore.getState().closeTabs(flowTestIds, collectionId);\n            }\n          }\n        }\n      }),\n    );\n  },\n  addOrUpdateEnvFile: (file, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          const existingEnv = collection.environments.find((e) => e.name === file.name && e.pathname === file.pathname);\n          if (existingEnv) {\n            existingEnv.modifiedAt = Date.now();\n            existingEnv.variables = file.variables;\n            console.log(`Collection env updated: ${JSON.stringify(existingEnv)}`);\n\n            // check if there are any open tabs, if yes mark them saved\n            const tab = useTabStore.getState().tabs.find((t) => t.id === existingEnv.id);\n            if (tab) {\n              useTabStore.getState().saveEnvTab(tab, cloneDeep(file.variables));\n            }\n          } else {\n            const timestamp = Date.now();\n            const env = {\n              id: uuidv4(),\n              type: OBJ_TYPES.environment,\n              createdAt: timestamp,\n              modifiedAt: timestamp,\n              ...file,\n            };\n            collection.environments.push(env);\n            console.log(`Collection env added: ${JSON.stringify(env)}`);\n            // check if there are any open tab requests\n            const event = useEventStore\n              .getState()\n              .events.find(\n                (e) => e.type === 'OPEN_NEW_ENVIRONMENT' && e.collectionId === collectionId && e.name === file.name,\n              );\n            if (event) {\n              useTabStore.getState().addEnvTab(env, collectionId);\n              useEventStore.getState().removeEvent(event.id);\n            }\n          }\n        }\n      }),\n    );\n  },\n  deleteEnvFile: (file, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection && collection.environments) {\n          const existingEnv = collection.environments.find((e) => e.name === file.name && e.pathname === file.pathname);\n          if (existingEnv) {\n            collection.environments = collection.environments.filter(\n              (e) => e.name !== file.name && e.pathname !== file.pathname,\n            );\n            console.log(`Collection env removed: ${JSON.stringify(existingEnv)}`);\n            // remove any open tab of this env\n            useTabStore.getState().closeTab(existingEnv.id, collectionId);\n          }\n        }\n      }),\n    );\n  },\n  addOrUpdateDotEnvVariables: (variables, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          collection.dotEnvVariables = variables;\n          console.log(`Collection dotenv variables added/updated: ${JSON.stringify(collection.dotEnvVariables)}`);\n        }\n      }),\n    );\n  },\n  createFlowTest: (file, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          const PATH_SEPARATOR = file.sep;\n          let currentPath = collection.pathname;\n          let currentSubItems = collection.items;\n          for (const directoryName of file.subDirectories) {\n            let childItem = currentSubItems.find((f) => f.type === OBJ_TYPES.folder && f.name === directoryName);\n            if (!childItem) {\n              childItem = {\n                id: uuidv4(),\n                pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`,\n                name: directoryName,\n                type: OBJ_TYPES.folder,\n                collapsed: true,\n                items: [],\n              };\n              currentSubItems.push(childItem);\n            }\n\n            currentPath = `${currentPath}${PATH_SEPARATOR}${directoryName}`;\n            currentSubItems = childItem.items;\n          }\n\n          if (!currentSubItems.find((f) => f.name === file.name)) {\n            const timestamp = Date.now();\n            const flowtest = {\n              id: uuidv4(),\n              type: OBJ_TYPES.flowtest,\n              createdAt: timestamp,\n              modifiedAt: timestamp,\n              name: file.name,\n              pathname: file.pathname,\n            };\n            currentSubItems.push(flowtest);\n            //console.log(`Collection updated: ${JSON.stringify(collection)}`);\n\n            // check if there are any open tab requests\n            const event = useEventStore\n              .getState()\n              .events.find(\n                (e) =>\n                  e.type === 'OPEN_NEW_FLOWTEST' &&\n                  e.collectionId === collectionId &&\n                  e.name === file.name &&\n                  e.path === currentPath,\n              );\n            if (event) {\n              useTabStore.getState().addFlowTestTab(\n                {\n                  ...flowtest,\n                  flowData: file.flowData,\n                },\n                collectionId,\n              );\n              useEventStore.getState().removeEvent(event.id);\n            }\n          }\n        }\n      }),\n    );\n  },\n  readFlowTest: (pathname, collectionId, flowData) => {\n    const collection = get().collections.find((c) => c.id === collectionId);\n\n    if (collection) {\n      const item = findItemInCollectionByPathname(collection, pathname);\n\n      if (item) {\n        // check if there are any open tab requests\n        const event = useEventStore\n          .getState()\n          .events.find(\n            (e) =>\n              e.type === 'OPEN_SAVED_FLOWTEST' &&\n              e.collectionId === collectionId &&\n              e.name === item.name &&\n              e.pathname === item.pathname,\n          );\n        if (event) {\n          useTabStore.getState().addFlowTestTab(\n            {\n              ...item,\n              flowData,\n            },\n            collectionId,\n          );\n          useEventStore.getState().removeEvent(event.id);\n        }\n      }\n    }\n  },\n  updateFlowTest: (file, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          const item = findItemInCollectionTree(file, collection);\n\n          if (item) {\n            item.modifiedAt = Date.now();\n            // console.log(`Collection updated: ${JSON.stringify(collection)}`);\n\n            // check if there are any open tabs, if yes mark them saved\n            const existingTab = useTabStore.getState().tabs.find((t) => t.id === item.id);\n            if (existingTab) {\n              useTabStore.getState().saveFlowTestTab(existingTab, cloneDeep(file.flowData));\n            }\n          }\n        }\n      }),\n    );\n  },\n  deleteFlowTest: (file, collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n\n        if (collection) {\n          const item = findItemInCollectionTree(file, collection);\n\n          if (item) {\n            deleteItemInCollectionByPathname(item.pathname, collection);\n            //console.log(`Collection updated: ${JSON.stringify(collection)}`);\n\n            // remove any open tab of this flowtest\n            useTabStore.getState().closeTab(item.id, collectionId);\n          }\n        }\n      }),\n    );\n  },\n  clickItem: (item, collectionId) => {\n    set(\n      produce((state) => {\n        if (item) {\n          if (item.type === OBJ_TYPES.collection) {\n            const collection = state.collections.find((c) => c.id === collectionId);\n            if (collection) {\n              collection.collapsed = !collection.collapsed;\n            }\n          } else if (item.type === OBJ_TYPES.folder) {\n            const collection = state.collections.find((c) => c.id === collectionId);\n            if (collection) {\n              const findItem = findItemInCollectionTree(item, collection);\n              findItem.collapsed = !findItem.collapsed;\n            }\n          }\n        }\n      }),\n    );\n  },\n  clickEnvironments: (collectionId) => {\n    set(\n      produce((state) => {\n        const collection = state.collections.find((c) => c.id === collectionId);\n        if (collection) {\n          collection.envCollapsed = !collection.envCollapsed;\n        }\n      }),\n    );\n  },\n}));\n\nexport default useCollectionStore;\n"
  },
  {
    "path": "src/stores/CommonStore.js",
    "content": "import { create } from 'zustand';\n\nconst useCommonStore = create((set) => ({\n  showLoader: false, // default value\n  setShowLoader: (showLoaderValue) => set(() => ({ showLoader: showLoaderValue })),\n}));\n\nexport default useCommonStore;\n"
  },
  {
    "path": "src/stores/EnvStore.js",
    "content": "import { create } from 'zustand';\nimport { useTabStore } from './TabStore';\n\nconst useEnvStore = create((set, get) => ({\n  variables: {},\n  setVariables: (variables) => {\n    set({ variables });\n  },\n  handleAddVariable: (key, value) => {\n    set((state) => ({ variables: { ...state.variables, [key]: value } }));\n    useTabStore.getState().updateEnvTab(useTabStore.getState().focusTabId, get().variables);\n  },\n  handleDeleteVariable: (key) => {\n    const { [key]: _, ...newVariables } = get().variables;\n    set({ variables: newVariables });\n    useTabStore.getState().updateEnvTab(useTabStore.getState().focusTabId, get().variables);\n  },\n}));\n\nexport default useEnvStore;\n"
  },
  {
    "path": "src/stores/EventListenerStore.js",
    "content": "import { create } from 'zustand';\n\nexport const useEventStore = create((set) => ({\n  events: [],\n  addEvent: (event) => {\n    set((state) => ({ events: [...state.events, event] }));\n  },\n  removeEvent: (eventId) => {\n    set((state) => ({ events: state.events.filter((e) => e.id != eventId) }));\n  },\n}));\n"
  },
  {
    "path": "src/stores/SettingsStore.js",
    "content": "import { create } from 'zustand';\n\nconst useSettingsStore = create((set, get) => ({\n  logSyncConfig: {},\n  genAIUsageDisclaimer: false,\n  appVersion: {},\n  addLogSyncConfig: (enabled, hostUrl, accessId, accessKey) => {\n    set((state) => ({ logSyncConfig: { ...state.logSyncConfig, ...{ enabled, hostUrl, accessId, accessKey } } }));\n  },\n  addGenAIUsageDisclaimer: (accepted) => {\n    set((state) => ({ genAIUsageDisclaimer: accepted }));\n  },\n  updateAppVersion: (version) => {\n    set((state) => ({ appVersion: version }));\n  },\n}));\n\nexport default useSettingsStore;\n"
  },
  {
    "path": "src/stores/TabStore.js",
    "content": "import { OBJ_TYPES } from 'constants/Common';\nimport { cloneDeep } from 'lodash';\nimport { create } from 'zustand';\nimport { produce } from 'immer';\n\nexport const useTabStore = create((set, get) => ({\n  tabs: [],\n  focusTabId: null,\n  selectedEnv: null,\n  addFlowTestTab: (flowtest, collectionId) => {\n    const existingTab = get().tabs.find(\n      (t) =>\n        t.id === flowtest.id &&\n        t.name === flowtest.name &&\n        t.pathname === flowtest.pathname &&\n        t.collectionId === collectionId &&\n        t.type === OBJ_TYPES.flowtest,\n    );\n    if (existingTab) {\n      set(() => ({ focusTabId: existingTab.id }));\n      return;\n    }\n    const newTab = {\n      id: flowtest.id,\n      collectionId: collectionId,\n      type: OBJ_TYPES.flowtest,\n      name: flowtest.name,\n      pathname: flowtest.pathname,\n      flowData: flowtest.flowData,\n      running: false,\n      run: {},\n    };\n\n    set((state) => ({ tabs: [...state.tabs, newTab] }));\n    set(() => ({ focusTabId: newTab.id }));\n  },\n  saveFlowTestTab: (tab, updatedFlowData) => {\n    set(\n      produce((state) => {\n        const existingTab = state.tabs.find(\n          (t) =>\n            t.id === tab.id &&\n            t.name === tab.name &&\n            t.pathname === tab.pathname &&\n            t.collectionId === tab.collectionId &&\n            tab.type === OBJ_TYPES.flowtest,\n        );\n        if (existingTab) {\n          existingTab.flowData = updatedFlowData;\n          existingTab.flowDataDraft = null;\n        }\n      }),\n    );\n  },\n  updateFlowTestNodes: (tabId, nodes) => {\n    set(\n      produce((state) => {\n        if (tabId) {\n          const existingTab = state.tabs.find((t) => t.id === tabId);\n          if (existingTab) {\n            if (!existingTab.flowDataDraft) {\n              existingTab.flowDataDraft = existingTab.flowData ? cloneDeep(existingTab.flowData) : {};\n            }\n            existingTab.flowDataDraft.nodes = nodes;\n          }\n        }\n      }),\n    );\n  },\n  updateFlowTestNode: (tabId, updatedNode) => {\n    set(\n      produce((state) => {\n        if (tabId) {\n          const existingTab = state.tabs.find((t) => t.id === tabId);\n          if (existingTab) {\n            if (!existingTab.flowDataDraft) {\n              existingTab.flowDataDraft = existingTab.flowData ? cloneDeep(existingTab.flowData) : {};\n            }\n            existingTab.flowDataDraft.nodes = existingTab.flowDataDraft.nodes.map((node, index) => {\n              if (node.id === updatedNode.id) {\n                return updatedNode;\n              }\n              return node;\n            });\n          }\n        }\n      }),\n    );\n  },\n  updateFlowTestEdges: (tabId, edges) => {\n    set(\n      produce((state) => {\n        if (tabId) {\n          const existingTab = state.tabs.find((t) => t.id === tabId);\n          if (existingTab) {\n            if (!existingTab.flowDataDraft) {\n              existingTab.flowDataDraft = existingTab.flowData ? cloneDeep(existingTab.flowData) : {};\n            }\n            existingTab.flowDataDraft.edges = edges;\n          }\n        }\n      }),\n    );\n  },\n  updateFlowTestViewport: (tabId, viewport) => {\n    set(\n      produce((state) => {\n        if (tabId) {\n          const existingTab = state.tabs.find((t) => t.id === tabId);\n          if (existingTab) {\n            if (!existingTab.flowDataDraft) {\n              existingTab.flowDataDraft = existingTab.flowData ? cloneDeep(existingTab.flowData) : {};\n            }\n            existingTab.flowDataDraft.viewport = viewport;\n          }\n        }\n      }),\n    );\n  },\n  updateFlowTestLogs: (tabId, status, logs, flowScan) => {\n    set(\n      produce((state) => {\n        const existingTab = state.tabs.find((t) => t.id === tabId);\n        if (existingTab) {\n          existingTab.run = {\n            status,\n            scan: flowScan,\n            logs,\n          };\n        }\n      }),\n    );\n  },\n  updateFlowTestRunStatus: (tabId, running) => {\n    set(\n      produce((state) => {\n        const existingTab = state.tabs.find((t) => t.id === tabId);\n        if (existingTab) {\n          existingTab.running = running;\n        }\n      }),\n    );\n  },\n  addEnvTab: (env, collectionId) => {\n    const existingTab = get().tabs.find(\n      (t) =>\n        t.id === env.id && t.name === env.name && t.collectionId === collectionId && t.type === OBJ_TYPES.environment,\n    );\n    if (existingTab) {\n      set(() => ({ focusTabId: existingTab.id }));\n      return;\n    }\n\n    const newTab = {\n      id: env.id,\n      collectionId: collectionId,\n      type: OBJ_TYPES.environment,\n      name: env.name,\n      variables: env.variables,\n    };\n\n    set((state) => ({ tabs: [...state.tabs, newTab] }));\n    set(() => ({ focusTabId: newTab.id }));\n  },\n  updateEnvTab: (tabId, variables) => {\n    set(\n      produce((state) => {\n        if (tabId) {\n          const existingTab = state.tabs.find((t) => t.id === tabId);\n          if (existingTab) {\n            if (!existingTab.variablesDraft) {\n              existingTab.variablesDraft = cloneDeep(existingTab.variables);\n            }\n            existingTab.variablesDraft = variables;\n          }\n        }\n      }),\n    );\n  },\n  saveEnvTab: (tab, variables) => {\n    set(\n      produce((state) => {\n        const existingTab = state.tabs.find(\n          (t) => t.id === tab.id && t.name === tab.name && t.type === OBJ_TYPES.environment,\n        );\n        if (existingTab) {\n          existingTab.variables = variables;\n          existingTab.variablesDraft = null;\n        }\n      }),\n    );\n  },\n  closeTab: (id, collectionId) => {\n    set((state) => ({ tabs: state.tabs.filter((t) => t.id !== id) }));\n    if (get().focusTabId === id) {\n      const tabs = get().tabs;\n      if (tabs && tabs.length) {\n        const collectionTabs = tabs.filter((t) => t.collectionId === collectionId);\n        if (collectionTabs && collectionTabs.length) {\n          set(() => ({ focusTabId: collectionTabs.slice(-1)[0].id }));\n        } else {\n          set(() => ({ focusTabId: tabs.slice(-1)[0].id }));\n        }\n      } else {\n        set(() => ({ focusTabId: null }));\n      }\n    }\n  },\n  closeTabs: (ids, collectionId) => {\n    set((state) => ({ tabs: state.tabs.filter((t) => !ids.includes(t.id)) }));\n    if (ids.includes(get().focusTabId)) {\n      const tabs = get().tabs;\n      if (tabs && tabs.length) {\n        const collectionTabs = tabs.filter((t) => t.collectionId === collectionId);\n        if (collectionTabs && collectionTabs.length) {\n          set(() => ({ focusTabId: collectionTabs.slice(-1)[0].id }));\n        } else {\n          set(() => ({ focusTabId: tabs.slice(-1)[0].id }));\n        }\n      } else {\n        set(() => ({ focusTabId: null }));\n      }\n    }\n  },\n  closeCollectionTabs: (collectionId) => {\n    set((state) => ({ tabs: state.tabs.filter((t) => t.collectionId != collectionId) }));\n    const tabs = get().tabs;\n    if (tabs && tabs.length) {\n      set(() => ({ focusTabId: tabs.slice(-1)[0].id }));\n    } else {\n      set(() => ({ focusTabId: null }));\n    }\n  },\n  setFocusTab: (id) => {\n    set(() => ({ focusTabId: id }));\n  },\n  setSelectedEnv: (name) => {\n    set(() => ({ selectedEnv: name }));\n  },\n}));\n"
  },
  {
    "path": "src/stores/collectionstore.test.js",
    "content": "import { act, renderHook } from '@testing-library/react';\nimport useCollectionStore from './CollectionStore';\n\ndescribe('Collection store', () => {\n  const collectionObj = {\n    version: '1',\n    id: '1',\n    type: 'collection',\n    name: 'collection',\n    pathname: '/parent/collection',\n    nodes: [],\n    items: [],\n    environments: [],\n  };\n\n  it('should correctly add and delete directory in the collection tree', () => {\n    const { result } = renderHook(() => useCollectionStore());\n\n    // create collection\n    act(() => {\n      result.current.createCollection('1', 'collection', '/parent/collection', []);\n    });\n    let collection = result.current.collections[0];\n    expect(collection).toEqual(collectionObj);\n\n    // Create a new folder inside collection\n    let directory = {\n      name: 'test-suite-1',\n      pathname: '/parent/collection/test-suite-1',\n    };\n    act(() => {\n      result.current.createFolder(directory, '1', ['test-suite-1'], '/');\n    });\n\n    collection = result.current.collections[0];\n    expect(collection.items[0].type).toEqual('folder');\n    expect(collection.items[0].name).toEqual(directory.name);\n    expect(collection.items[0].pathname).toEqual(directory.pathname);\n\n    directory = {\n      name: 'test-suite-3',\n      pathname: '/parent/collection/test-suite-1/test-suite-2/test-suite-3',\n    };\n    act(() => {\n      result.current.createFolder(directory, '1', ['test-suite-1', 'test-suite-2', 'test-suite-3'], '/');\n    });\n    collection = result.current.collections[0];\n    expect(collection.items[0].items[0].name).toEqual('test-suite-2');\n    expect(collection.items[0].items[0].pathname).toEqual('/parent/collection/test-suite-1/test-suite-2');\n\n    expect(collection.items[0].items[0].items[0].name).toEqual(directory.name);\n    expect(collection.items[0].items[0].items[0].pathname).toEqual(directory.pathname);\n\n    directory = {\n      name: 'test-suite-2',\n      pathname: '/parent/collection/test-suite-1/test-suite-2',\n    };\n    act(() => {\n      result.current.deleteFolder(directory, '1');\n    });\n    collection = result.current.collections[0];\n    expect(collection.items[0].items).toEqual([]);\n\n    directory = {\n      name: 'collection',\n      pathname: '/parent/collection',\n    };\n    act(() => {\n      result.current.deleteFolder(directory, '1');\n    });\n    expect(result.current.collections).toEqual([]);\n  });\n\n  it('should correctly add/delete/change file in the collection tree', () => {\n    const { result } = renderHook(() => useCollectionStore());\n\n    // create collection\n    act(() => {\n      result.current.createCollection('1', 'collection', '/parent/collection', []);\n    });\n\n    let directory = {\n      name: 'test-suite-3',\n      pathname: '/parent/collection/test-suite-1/test-suite-2/test-suite-3',\n    };\n    act(() => {\n      result.current.createFolder(directory, '1', ['test-suite-1', 'test-suite-2', 'test-suite-3'], '/');\n    });\n\n    act(() => {\n      result.current.createFlowTest(\n        {\n          name: 'test1.flow',\n          pathname: '/parent/collection/test-suite-1/test1.flow',\n          subDirectories: ['test-suite-1'],\n          sep: '/',\n        },\n        collectionObj.id,\n      );\n    });\n\n    act(() => {\n      result.current.createFlowTest(\n        {\n          name: 'test2.flow',\n          pathname: '/parent/collection/test-suite-1/test-suite-2/test2.flow',\n          subDirectories: ['test-suite-1', 'test-suite-2'],\n          sep: '/',\n        },\n        collectionObj.id,\n      );\n    });\n\n    act(() => {\n      result.current.createFlowTest(\n        {\n          name: 'test3.flow',\n          pathname: '/parent/collection/test-suite-1/test-suite-2/test-suite-3/test3.flow',\n          subDirectories: ['test-suite-1', 'test-suite-2', 'test-suite-3'],\n          sep: '/',\n        },\n        collectionObj.id,\n      );\n    });\n\n    const collection = result.current.collections[0];\n    const file1ModifiedTime = collection.items[0].items[1].modifiedAt;\n    expect(collection.items[0].items[1].type).toEqual('flowtest');\n    expect(collection.items[0].items[1].name).toEqual('test1.flow');\n    expect(collection.items[0].items[1].pathname).toEqual('/parent/collection/test-suite-1/test1.flow');\n\n    expect(collection.items[0].items[0].items[1].type).toEqual('flowtest');\n    expect(collection.items[0].items[0].items[1].name).toEqual('test2.flow');\n    expect(collection.items[0].items[0].items[1].pathname).toEqual(\n      '/parent/collection/test-suite-1/test-suite-2/test2.flow',\n    );\n\n    expect(collection.items[0].items[0].items[0].items[0].type).toEqual('flowtest');\n    expect(collection.items[0].items[0].items[0].items[0].name).toEqual('test3.flow');\n    expect(collection.items[0].items[0].items[0].items[0].pathname).toEqual(\n      '/parent/collection/test-suite-1/test-suite-2/test-suite-3/test3.flow',\n    );\n\n    act(() => {\n      result.current.updateFlowTest(\n        {\n          name: 'test1.flow',\n          pathname: '/parent/collection/test-suite-1/test1.flow',\n        },\n        collectionObj.id,\n      );\n    });\n    expect(result.current.collections[0].items[0].items[1].modifiedAt > file1ModifiedTime).toEqual(true);\n\n    act(() => {\n      result.current.deleteFlowTest(\n        {\n          name: 'test1.flow',\n          pathname: '/parent/collection/test-suite-1/test1.flow',\n        },\n        collectionObj.id,\n      );\n    });\n    expect(result.current.collections[0].items[0].items[1]).toEqual(undefined);\n\n    directory = {\n      name: 'collection',\n      pathname: '/parent/collection',\n    };\n    act(() => {\n      result.current.deleteFolder(directory, '1');\n    });\n    expect(result.current.collections).toEqual([]);\n  });\n\n  it('should add environment and dotEnv file correctly in collection tree', () => {\n    const { result } = renderHook(() => useCollectionStore());\n\n    // create collection\n    act(() => {\n      result.current.createCollection('1', 'collection', '/parent/collection', []);\n    });\n\n    let directory = {\n      name: 'test-suite-3',\n      pathname: '/parent/collection/test-suite-1/test-suite-2/test-suite-3',\n    };\n    act(() => {\n      result.current.createFolder(directory, '1', ['test-suite-1', 'test-suite-2', 'test-suite-3'], '/');\n    });\n\n    const variables = {\n      k1: 'v1',\n      k2: 'v2',\n    };\n    act(() => {\n      result.current.addOrUpdateEnvFile(\n        {\n          name: 'staging.env',\n          pathname: '/parent/collection/environments/staging.env',\n          variables,\n        },\n        collectionObj.id,\n      );\n    });\n\n    const dotEnvVars = {\n      k: 'v',\n      v: 'k',\n    };\n    act(() => {\n      result.current.addOrUpdateDotEnvVariables(dotEnvVars, collectionObj.id);\n    });\n\n    const collection = result.current.collections[0];\n    expect(collection.environments[0].type).toEqual('environment');\n    expect(collection.environments[0].variables).toEqual(variables);\n    expect(collection.dotEnvVariables).toEqual(dotEnvVars);\n\n    act(() => {\n      result.current.addOrUpdateEnvFile(\n        {\n          name: 'staging.env',\n          pathname: '/parent/collection/environments/staging.env',\n          variables: {\n            ...variables,\n            k3: 'v3',\n          },\n        },\n        collectionObj.id,\n      );\n    });\n\n    act(() => {\n      result.current.addOrUpdateDotEnvVariables(\n        {\n          ...dotEnvVars,\n          t: 'k',\n        },\n        collectionObj.id,\n      );\n    });\n\n    expect(result.current.collections[0].environments[0].type).toEqual('environment');\n    expect(result.current.collections[0].environments[0].variables).toEqual({\n      ...variables,\n      k3: 'v3',\n    });\n    expect(result.current.collections[0].dotEnvVariables).toEqual({\n      ...dotEnvVars,\n      t: 'k',\n    });\n\n    act(() => {\n      result.current.deleteEnvFile(\n        {\n          name: 'staging.env',\n          pathname: '/parent/collection/environments/staging.env',\n        },\n        collectionObj.id,\n      );\n    });\n    expect(result.current.collections[0].environments).toEqual([]);\n\n    // try to remove a env file should not error out\n    act(() => {\n      result.current.deleteEnvFile(\n        {\n          name: 'staging.env',\n          pathname: '/parent/collection/environments/staging.env',\n        },\n        collectionObj.id,\n      );\n    });\n    expect(result.current.collections[0].environments).toEqual([]);\n\n    directory = {\n      name: 'collection',\n      pathname: '/parent/collection',\n    };\n    act(() => {\n      result.current.deleteFolder(directory, '1');\n    });\n    expect(result.current.collections).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "src/stores/eventstore.test.js",
    "content": "import { act, renderHook } from '@testing-library/react';\nimport { useEventStore } from './EventListenerStore';\n\ndescribe('Event store', () => {\n  const openNewFlowTestEvent = {\n    id: '1',\n    type: 'OPEN_NEW_FLOWTEST',\n    collectionId: '2',\n    name: 'test',\n    path: '/parent/test-folder',\n  };\n\n  const openSavedFlowTestEvent = {\n    id: '2',\n    type: 'OPEN_SAVED_FLOWTEST',\n    collectionId: '3',\n    name: 'test1',\n    path: '/parent/test-folder1',\n  };\n\n  it('should correctly add and delete events', () => {\n    const { result } = renderHook(() => useEventStore());\n\n    // create events\n    act(() => {\n      result.current.addEvent(openNewFlowTestEvent);\n      result.current.addEvent(openSavedFlowTestEvent);\n    });\n    expect(result.current.events).toEqual([openNewFlowTestEvent, openSavedFlowTestEvent]);\n\n    // remove events\n    act(() => {\n      result.current.removeEvent(openNewFlowTestEvent.id);\n    });\n    expect(result.current.events).toEqual([openSavedFlowTestEvent]);\n\n    act(() => {\n      result.current.removeEvent(openSavedFlowTestEvent.id);\n    });\n    expect(result.current.events).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "src/stores/tabstore.test.js",
    "content": "import { act, renderHook } from '@testing-library/react';\nimport { useTabStore } from './TabStore';\n\ndescribe('Tab store', () => {\n  const flowtest = {\n    id: '1',\n    name: 'test',\n    pathname: '/parent/collection/test.flow',\n  };\n\n  const flowtest1 = {\n    id: '2',\n    name: 'test1',\n    pathname: '/parent/collection1/test1.flow',\n  };\n\n  const flowtest2 = {\n    id: '3',\n    name: 'test2',\n    pathname: '/parent/collection1/test2.flow',\n  };\n\n  const env = {\n    id: '9',\n    name: 'test',\n    pathname: '/parent/collection1/environments/test.env',\n  };\n\n  const collectionId = '1234';\n  const collectionId1 = '12345';\n\n  it('should correctly add and delete tabs', () => {\n    const { result } = renderHook(() => useTabStore());\n\n    // create new flowtest tab\n    act(() => {\n      result.current.addFlowTestTab(flowtest, collectionId);\n    });\n    let tabs = result.current.tabs;\n    expect(tabs[0].id).toEqual(flowtest.id);\n    expect(tabs[0].collectionId).toEqual(collectionId);\n    expect(tabs[0].type).toEqual('flowtest');\n    expect(tabs[0].name).toEqual(flowtest.name);\n    expect(tabs[0].pathname).toEqual(flowtest.pathname);\n    expect(tabs[0].flowData).toEqual(undefined);\n    expect(result.current.focusTabId).toEqual(flowtest.id);\n\n    // create new flowtest tab for different collection\n    act(() => {\n      result.current.addFlowTestTab(flowtest1, collectionId1);\n      result.current.addFlowTestTab(flowtest2, collectionId1);\n    });\n    expect(result.current.focusTabId).toEqual(flowtest2.id);\n\n    act(() => {\n      result.current.addEnvTab(env, collectionId1);\n    });\n    expect(result.current.focusTabId).toEqual(env.id);\n\n    act(() => {\n      result.current.closeTab(env.id, collectionId1);\n    });\n    expect(result.current.focusTabId).toEqual(flowtest2.id);\n\n    act(() => {\n      result.current.closeTab(flowtest2.id, collectionId1);\n    });\n    expect(result.current.focusTabId).toEqual(flowtest1.id);\n\n    act(() => {\n      result.current.closeTab(flowtest1.id, collectionId1);\n    });\n    expect(result.current.focusTabId).toEqual(flowtest.id);\n  });\n\n  it('should correctly close all tabs of a collection', () => {\n    const { result } = renderHook(() => useTabStore());\n\n    act(() => {\n      for (let i = 0; i < 3; i++) {\n        result.current.addFlowTestTab(\n          {\n            id: `${i}`,\n            name: `test${i}`,\n            pathname: `/parent/collection/test${i}.flow`,\n          },\n          collectionId1,\n        );\n      }\n    });\n    expect(result.current.tabs.length).toEqual(4);\n\n    act(() => {\n      result.current.closeCollectionTabs(collectionId1);\n    });\n    expect(result.current.tabs.length).toEqual(1);\n    expect(result.current.focusTabId).toEqual(flowtest.id);\n  });\n\n  it('should correctly close a set of tabs', () => {\n    const { result } = renderHook(() => useTabStore());\n\n    act(() => {\n      for (let i = 0; i < 3; i++) {\n        result.current.addFlowTestTab(\n          {\n            id: `${i}`,\n            name: `test${i}`,\n            pathname: `/parent/collection/test${i}.flow`,\n          },\n          collectionId1,\n        );\n      }\n    });\n    expect(result.current.tabs.length).toEqual(4);\n\n    act(() => {\n      result.current.closeTabs(['0', '2'], collectionId1);\n    });\n    expect(result.current.tabs.length).toEqual(2);\n    expect(result.current.focusTabId).toEqual('1');\n  });\n});\n"
  },
  {
    "path": "src/stores/utils.js",
    "content": "import { OBJ_TYPES } from 'constants/Common';\n\nexport const findItemInCollectionTree = (item, collection) => {\n  let flattenedItems = flattenItems(collection.items);\n\n  return flattenedItems.find((i) => i.pathname === item.pathname && i.name === item.name);\n};\n\nexport const findItemInCollectionByPathname = (collection, pathname) => {\n  if (collection.pathname === pathname) {\n    return collection;\n  } else {\n    const flattenedItems = flattenItems(collection.items);\n    return flattenedItems.find((i) => i.pathname === pathname);\n  }\n};\n\nexport const deleteItemInCollectionByPathname = (pathname, collection) => {\n  collection.items = collection.items.filter((i) => i.pathname !== pathname);\n\n  let flattenedItems = flattenItems(collection.items);\n  flattenedItems.forEach((i) => {\n    if (i.items && i.items.length) {\n      i.items = i.items.filter((i) => i.pathname !== pathname);\n    }\n  });\n};\n\nexport const getAllFlowTests = (collection) => {\n  if (collection) {\n    let flattenedItems = flattenItems(collection.items);\n\n    return flattenedItems.filter((i) => i.type === OBJ_TYPES.flowtest).map((i) => i.pathname);\n  }\n};\n\nexport const flattenItems = (items = []) => {\n  const flattenedItems = [];\n\n  const flatten = (itms, flattened) => {\n    itms.forEach((i) => {\n      flattened.push(i);\n\n      if (i.items && i.items.length) {\n        flatten(i.items, flattened);\n      }\n    });\n  };\n\n  flatten(items, flattenedItems);\n\n  return flattenedItems;\n};\n"
  },
  {
    "path": "src/utils/common.js",
    "content": "export const getInputType = (type) => {\n  if (type == 'Number') {\n    return 'number';\n  } else {\n    return 'text';\n  }\n};\n\nexport const getDefaultValue = (type) => {\n  if (type === 'Number') {\n    return 0;\n  } else if (type === 'Boolean') {\n    return false;\n  } else {\n    return '';\n  }\n};\n\nexport const promiseWithTimeout = (promise, ms) => {\n  // Create a new promise that rejects in <ms> milliseconds\n  let timeout = new Promise((resolve, reject) => {\n    let id = setTimeout(() => {\n      clearTimeout(id);\n      reject(new Error('Timed out in ' + ms + 'ms.'));\n    }, ms);\n  });\n\n  // Returns a race between our timeout and the passed in promise\n  return Promise.race([promise, timeout]);\n};\n\n/**\n * Utility Function to check whether an object is Empty or not\n *\n * @param obj :  a JS object to check\n *\n * @returns True or False on the basis of whether an Object is empty or not.\n */\nexport const isEmptyObj = (obj) => {\n  for (var prop in obj) {\n    // eslint-disable-next-line no-prototype-builtins\n    if (obj.hasOwnProperty(prop)) {\n      return false;\n    }\n  }\n  return JSON.stringify(obj) === JSON.stringify({});\n};\n\nexport const formatTimeStamp = (timeStamp) => {\n  let formattedDate = new Date(timeStamp);\n  return formattedDate.toGMTString();\n};\n"
  },
  {
    "path": "src/utils/useRenderCount.js",
    "content": "import { useEffect, useRef } from 'react';\n\n/**\n * This is utility component/hook which can be used by any view component to check the number of times it is getting rendered\n * @returns\n */\nexport const useRenderCount = () => {\n  const ref = useRef(0);\n\n  useEffect(() => {\n    ref.current += 1;\n  });\n\n  return ref.current;\n};\n\nexport default useRenderCount;\n"
  },
  {
    "path": "src/utils/useTelemetry.js",
    "content": "/**\n * Telemetry in FlowTestAI is just an anonymous visit counter (triggered once per day).\n * The only details shared are:\n *      - OS (ex: mac, windows, linux)\n *      - Version (ex: 1.3.0)\n * We don't track usage analytics / micro-interactions / crash logs / anything else.\n */\n\nimport { useEffect } from 'react';\nimport { v4 as uuidv4 } from 'uuid';\n// import getConfig from 'next/config';\nimport { PostHog } from 'posthog-node';\n\nconst posthogApiKey = 'phc_NWEriNXcUcZ1QB3GDBHrM3NDzZj69P1npzZ9PY2A5tW';\nlet posthogClient = null;\n\nconst getPosthogClient = () => {\n  if (posthogClient) {\n    return posthogClient;\n  }\n\n  posthogClient = new PostHog(posthogApiKey);\n  return posthogClient;\n};\n\nconst getAnonymousId = () => {\n  let id = localStorage.getItem('flowtestai.anonymousId');\n\n  if (!id || !id.length || id.length !== 21) {\n    id = uuidv4();\n    localStorage.setItem('flowtestai.anonymousId', id);\n  }\n\n  return id;\n};\n\nconst trackStart = () => {\n  const { ipcRenderer } = window;\n  const id = getAnonymousId();\n  const client = getPosthogClient();\n  client.capture({\n    distinctId: id,\n    event: 'start',\n    properties: {\n      os: ipcRenderer.isMacOs() ? 'darwin' : 'win32',\n      version: '1.2.0',\n    },\n  });\n};\n\nconst useTelemetry = () => {\n  useEffect(() => {\n    trackStart();\n    setInterval(trackStart, 24 * 60 * 60 * 1000);\n  }, []);\n};\n\nexport default useTelemetry;\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\n\nmodule.exports = {\n  content: ['./src/**/*.{js,jsx,ts,tsx}', './public/*.html'],\n  darkMode: 'class',\n  theme: {\n    extend: {\n      colors: {\n        denim: {\n          // accent or secondary // indigo can also be used\n          50: '#f1f5fd',\n          100: '#e0eaf9',\n          200: '#c7d9f6',\n          300: '#a1c1ef',\n          400: '#74a0e6',\n          500: '#5480dd',\n          600: '#3b61d0',\n          700: '#3652bf',\n          800: '#31449c',\n          900: '#2c3c7c',\n          950: '#1f264c',\n        },\n        fog: {\n          50: '#f6f8f9',\n          100: '#ebeff3',\n          200: '#d3dce4',\n          300: '#acbecd',\n          400: '#7f9bb1',\n          500: '#5f7f98',\n          600: '#4b677e',\n          700: '#435a6f',\n          800: '#364756',\n          900: '#303d4a',\n          950: '#202831',\n        },\n        background: {\n          lighter: '#f8fafc', // slate-50\n          light: '#f1f5f9', // slate-100\n          DEFAULT: '#e2e8f0', // slate-200\n          dark: '#cbd5e1', // slate-300\n        },\n        // divider: {\n        //   light: '#f1f5f9',\n        //   dark: '#cbd5e1',\n        //   DEFAULT: '#f1f5f9',\n        // },\n\n        // primary: {\n        //   light: '#f8fafc',\n        //   dark: '#f1f5f9',\n        //   contrast: '#fdfdfd',\n        //   DEFAULT: '#fff',\n        // },\n        // secondary: {\n        //   light: '#94a3b8',\n        //   dark: '#0f172a',\n        //   DEFAULT: '#64748b',\n        // },\n        // accent: {\n        //   light: '#74a0e6',\n        //   dark: '#3b61d0',\n        //   contrast: '#fff',\n        //   DEFAULT: '#5480dd',\n        // },\n      },\n    },\n    fontFamily: {\n      openSans: ['Open Sans', 'sans-serif'],\n      anticDidone: ['Antic Didone', 'serif'],\n      montserrat: ['Montserrat', 'sans-serif'],\n    },\n  },\n  plugins: [require('daisyui')],\n  daisyui: {\n    // themes: false,\n    themes: [],\n    // false: only light + dark | true: all themes | array: specific themes like this [\"light\", \"dark\", \"cupcake\"]\n    // darkTheme: 'dark',\n    // name of one of the included themes for dark mode\n    base: true,\n    // applies background color and foreground color for root element by default\n    styled: true,\n    // include daisyUI colors and design decisions for all components\n    utils: true,\n    // adds responsive and modifier utility classes\n    prefix: '',\n    // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)\n    logs: true,\n    // Shows info about daisyUI version and used config in the console when building your CSS\n    themeRoot: ':root', // The element that receives theme color CSS variables\n  },\n};\n"
  }
]