From f53768612b1e2268697b5444e502032ef9f3fb3c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 23 May 2023 23:45:12 -0400 Subject: copying from old repo --- extension/react-app/.gitignore | 24 + extension/react-app/index.html | 13 + extension/react-app/package-lock.json | 5262 ++++++++++++++++++++ extension/react-app/package.json | 32 + extension/react-app/postcss.config.cjs | 6 + extension/react-app/public/continue_arrow.png | Bin 0 -> 1350 bytes extension/react-app/public/vite.svg | 1 + extension/react-app/src/App.tsx | 29 + extension/react-app/src/TestPage.tsx | 33 + extension/react-app/src/assets/Hubot-Sans.woff2 | Bin 0 -> 165932 bytes extension/react-app/src/assets/Mona-Sans.woff2 | Bin 0 -> 133748 bytes extension/react-app/src/assets/react.svg | 1 + extension/react-app/src/components/CodeBlock.tsx | 57 + .../react-app/src/components/CodeMultiselect.tsx | 276 + .../react-app/src/components/ContinueButton.tsx | 37 + extension/react-app/src/components/DebugPanel.tsx | 121 + .../src/components/IterationContainer.tsx | 77 + .../react-app/src/components/StepContainer.tsx | 208 + .../react-app/src/components/SubContainer.tsx | 24 + .../react-app/src/components/VSCodeFileLink.tsx | 17 + extension/react-app/src/components/index.ts | 136 + extension/react-app/src/continue_arrow.png | Bin 0 -> 1350 bytes extension/react-app/src/highlight/dark.min.css | 53 + extension/react-app/src/hooks/useArrayState.ts | 29 + extension/react-app/src/hooks/useWebsocket.ts | 67 + extension/react-app/src/index.css | 34 + extension/react-app/src/main.tsx | 10 + extension/react-app/src/redux/hooks.ts | 21 + .../react-app/src/redux/selectors/chatSelectors.ts | 11 + .../src/redux/selectors/debugContextSelectors.ts | 29 + .../react-app/src/redux/selectors/miscSelectors.ts | 5 + extension/react-app/src/redux/slices/chatSlice.ts | 93 + .../react-app/src/redux/slices/configSlice.ts | 45 + .../react-app/src/redux/slices/debugContexSlice.ts | 149 + extension/react-app/src/redux/slices/miscSlice.ts | 16 + extension/react-app/src/redux/store.ts | 43 + extension/react-app/src/tabs/additionalContext.tsx | 18 + extension/react-app/src/tabs/chat/MessageDiv.tsx | 73 + extension/react-app/src/tabs/chat/index.tsx | 267 + extension/react-app/src/tabs/main.tsx | 189 + extension/react-app/src/tabs/notebook.tsx | 285 ++ extension/react-app/src/tabs/welcome.tsx | 22 + extension/react-app/src/util/api.ts | 43 + extension/react-app/src/util/editCache.ts | 89 + extension/react-app/src/util/index.ts | 27 + extension/react-app/src/vite-env.d.ts | 1 + extension/react-app/src/vscode/index.ts | 47 + extension/react-app/tailwind.config.cjs | 22 + extension/react-app/tsconfig.json | 21 + extension/react-app/tsconfig.node.json | 9 + extension/react-app/vite.config.ts | 18 + 51 files changed, 8090 insertions(+) create mode 100644 extension/react-app/.gitignore create mode 100644 extension/react-app/index.html create mode 100644 extension/react-app/package-lock.json create mode 100644 extension/react-app/package.json create mode 100644 extension/react-app/postcss.config.cjs create mode 100644 extension/react-app/public/continue_arrow.png create mode 100644 extension/react-app/public/vite.svg create mode 100644 extension/react-app/src/App.tsx create mode 100644 extension/react-app/src/TestPage.tsx create mode 100644 extension/react-app/src/assets/Hubot-Sans.woff2 create mode 100644 extension/react-app/src/assets/Mona-Sans.woff2 create mode 100644 extension/react-app/src/assets/react.svg create mode 100644 extension/react-app/src/components/CodeBlock.tsx create mode 100644 extension/react-app/src/components/CodeMultiselect.tsx create mode 100644 extension/react-app/src/components/ContinueButton.tsx create mode 100644 extension/react-app/src/components/DebugPanel.tsx create mode 100644 extension/react-app/src/components/IterationContainer.tsx create mode 100644 extension/react-app/src/components/StepContainer.tsx create mode 100644 extension/react-app/src/components/SubContainer.tsx create mode 100644 extension/react-app/src/components/VSCodeFileLink.tsx create mode 100644 extension/react-app/src/components/index.ts create mode 100644 extension/react-app/src/continue_arrow.png create mode 100644 extension/react-app/src/highlight/dark.min.css create mode 100644 extension/react-app/src/hooks/useArrayState.ts create mode 100644 extension/react-app/src/hooks/useWebsocket.ts create mode 100644 extension/react-app/src/index.css create mode 100644 extension/react-app/src/main.tsx create mode 100644 extension/react-app/src/redux/hooks.ts create mode 100644 extension/react-app/src/redux/selectors/chatSelectors.ts create mode 100644 extension/react-app/src/redux/selectors/debugContextSelectors.ts create mode 100644 extension/react-app/src/redux/selectors/miscSelectors.ts create mode 100644 extension/react-app/src/redux/slices/chatSlice.ts create mode 100644 extension/react-app/src/redux/slices/configSlice.ts create mode 100644 extension/react-app/src/redux/slices/debugContexSlice.ts create mode 100644 extension/react-app/src/redux/slices/miscSlice.ts create mode 100644 extension/react-app/src/redux/store.ts create mode 100644 extension/react-app/src/tabs/additionalContext.tsx create mode 100644 extension/react-app/src/tabs/chat/MessageDiv.tsx create mode 100644 extension/react-app/src/tabs/chat/index.tsx create mode 100644 extension/react-app/src/tabs/main.tsx create mode 100644 extension/react-app/src/tabs/notebook.tsx create mode 100644 extension/react-app/src/tabs/welcome.tsx create mode 100644 extension/react-app/src/util/api.ts create mode 100644 extension/react-app/src/util/editCache.ts create mode 100644 extension/react-app/src/util/index.ts create mode 100644 extension/react-app/src/vite-env.d.ts create mode 100644 extension/react-app/src/vscode/index.ts create mode 100644 extension/react-app/tailwind.config.cjs create mode 100644 extension/react-app/tsconfig.json create mode 100644 extension/react-app/tsconfig.node.json create mode 100644 extension/react-app/vite.config.ts (limited to 'extension/react-app') diff --git a/extension/react-app/.gitignore b/extension/react-app/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/extension/react-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/extension/react-app/index.html b/extension/react-app/index.html new file mode 100644 index 00000000..e0d1c840 --- /dev/null +++ b/extension/react-app/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json new file mode 100644 index 00000000..1ba8cfe8 --- /dev/null +++ b/extension/react-app/package-lock.json @@ -0,0 +1,5262 @@ +{ + "name": "react-app", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "react-app", + "version": "0.0.0", + "dependencies": { + "@styled-icons/heroicons-outline": "^10.47.0", + "@types/vscode-webview": "^1.57.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^8.0.5", + "react-redux": "^8.0.5", + "styled-components": "^5.3.6", + "vscode-webview": "^1.0.1-beta.1" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@types/styled-components": "^5.1.26", + "@vitejs/plugin-react-swc": "^3.0.0", + "autoprefixer": "^10.4.13", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7", + "typescript": "^4.9.3", + "vite": "^4.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "dependencies": { + "@babel/types": "^7.21.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", + "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.1", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.2", + "@babel/types": "^7.21.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", + "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@styled-icons/heroicons-outline": { + "version": "10.47.0", + "resolved": "https://registry.npmjs.org/@styled-icons/heroicons-outline/-/heroicons-outline-10.47.0.tgz", + "integrity": "sha512-C7bcJ/YPVKACoQLSBDM9DkgmrO1HmqAaru9kqYK2V+GaGUW5raf6dyTLo7GUYt/CJ7p3qWxsBz3SX6djzdaODQ==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@styled-icons/styled-icon": "^10.7.0" + }, + "funding": { + "type": "GitHub", + "url": "https://github.com/sponsors/jacobwgillespie" + }, + "peerDependencies": { + "react": "*", + "styled-components": "*" + } + }, + "node_modules/@styled-icons/styled-icon": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/@styled-icons/styled-icon/-/styled-icon-10.7.0.tgz", + "integrity": "sha512-SCrhCfRyoY8DY7gUkpz+B0RqUg/n1Zaqrr2+YKmK/AyeNfCcoHuP4R9N4H0p/NA1l7PTU10ZkAWSLi68phnAjw==", + "dependencies": { + "@babel/runtime": "^7.19.0" + }, + "funding": { + "type": "GitHub", + "url": "https://github.com/sponsors/jacobwgillespie" + }, + "peerDependencies": { + "react": "*", + "styled-components": ">=4.1.0 <6" + } + }, + "node_modules/@swc/core": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.36.tgz", + "integrity": "sha512-Ogrd9uRNIj7nHjXxG66UlKBIcXESUenJ7OD6K2a8p82qlg6ne7Ne5Goiipm/heHYhSfVmjcnRWL9ZJ4gv+YCPA==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.36", + "@swc/core-darwin-x64": "1.3.36", + "@swc/core-linux-arm-gnueabihf": "1.3.36", + "@swc/core-linux-arm64-gnu": "1.3.36", + "@swc/core-linux-arm64-musl": "1.3.36", + "@swc/core-linux-x64-gnu": "1.3.36", + "@swc/core-linux-x64-musl": "1.3.36", + "@swc/core-win32-arm64-msvc": "1.3.36", + "@swc/core-win32-ia32-msvc": "1.3.36", + "@swc/core-win32-x64-msvc": "1.3.36" + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.36.tgz", + "integrity": "sha512-lsP+C8p9cC/Vd9uAbtxpEnM8GoJI/MMnVuXak7OlxOtDH9/oTwmAcAQTfNGNaH19d2FAIRwf+5RbXCPnxa2Zjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.36.tgz", + "integrity": "sha512-jaLXsozWN5xachl9fPxDMi5nbWq1rRxPAt6ISeiYB6RJk0MQKH1634pOweBBem2pUDDzwDFXFw6f22LTm/cFvA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.36.tgz", + "integrity": "sha512-vcBdTHjoEpvJDbFlgto+S6VwAHzLA9GyCiuNcTU2v4KNQlFzhbO4A4PMfMCb/Z0RLJEr16tirfHdWIxjU3h8nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.36.tgz", + "integrity": "sha512-o7f5OsvwWppJo+qIZmrGO5+XC6DPt6noecSbRHjF6o1YAcR13ETPC14k1eC9H1YbQwpyCFNVAFXyNcUbCeQyrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.36.tgz", + "integrity": "sha512-FSHPngMi3c0fuGt9yY2Ubn5UcELi3EiPLJxBSC3X8TF9atI/WHZzK9PE9Gtn0C/LyRh4CoyOugDtSOPzGYmLQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.36.tgz", + "integrity": "sha512-PHSsH2rek5pr3e0K09VgWAbrWK2vJhaI7MW9TPoTjyACYjcs3WwjcjQ30MghXUs2Dc/bXjWAOi9KFTjq/uCyFg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.36.tgz", + "integrity": "sha512-4LfMYQHzozHCKkIcmQy83b+4SpI+mOp6sYNbXqSRz5dYvTVjegKZXe596P1U/87cK2cgR4uYvkgkgBXquaWvwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.36.tgz", + "integrity": "sha512-7y3dDcun79TAjCyk3Iv0eOMw1X/KNQbkVyKOGqnEgq9g22F8F1FoUGKHNTzUqVdzpHeJSsHgW5PlkEkl3c/d9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.36.tgz", + "integrity": "sha512-zK0VR3B4LX5hzQ+7eD+K+FkxJlJg5Lo36BeahMzQ+/i0IURpnuyFlW88sdkFkMsc2swdU6bpvxLZeIRQ3W4OUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.36.tgz", + "integrity": "sha512-2bIjr9DhAckGiXZEvj6z2z7ECPcTimG+wD0VuQTvr+wkx46uAJKl5Kq+Zk+dd15ErL7JGUtCet1T7bf1k4FwvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "devOptional": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "node_modules/@types/vscode-webview": { + "version": "1.57.1", + "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz", + "integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ==" + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", + "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.35" + }, + "peerDependencies": { + "vite": "^4" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-convert/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.311", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", + "integrity": "sha512-RoDlZufvrtr2Nx3Yx5MB8jX3aHIxm8nRWPJm3yVvyHmyKaRvn90RjzB6hNnt0AkhS3IInJdyRfQb4mWhPvUjVw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", + "integrity": "sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz", + "integrity": "sha512-tGvhT94e+cVnQt8JWE9/b3cUQZWS732TJxXHktvP+BYo62PpYD53Ls/6cC60rW21dW+txxiM4zMdc6abASvZKA==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-markdown": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz", + "integrity": "sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.17.2.tgz", + "integrity": "sha512-qMNZdlQPCkWodrAZ3qnJtvCAl4vpQ8q77uEujVCCbC/6CLB7Lcmvjq7HyiOSnf4fxTT9XgsE36oLHJBH49xjqA==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", + "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "dev": true, + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.0.9", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.4.tgz", + "integrity": "sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==", + "dev": true, + "dependencies": { + "esbuild": "^0.16.14", + "postcss": "^8.4.21", + "resolve": "^1.22.1", + "rollup": "^3.10.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vscode-webview": { + "version": "1.0.1-beta.1", + "resolved": "https://registry.npmjs.org/vscode-webview/-/vscode-webview-1.0.1-beta.1.tgz", + "integrity": "sha512-8H2icqlb93qIvZs7Me/0sALMqJrX2e2W7JVE0mfK8exUPETP9Bmg+feivaByd1Xbg78lcKaNF9ZRrIFSenUVxQ==", + "dependencies": { + "fs-extra": "^10.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/generator": { + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "requires": { + "@babel/types": "^7.21.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==" + }, + "@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", + "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.1", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.2", + "@babel/types": "^7.21.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", + "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "dev": true, + "optional": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@styled-icons/heroicons-outline": { + "version": "10.47.0", + "resolved": "https://registry.npmjs.org/@styled-icons/heroicons-outline/-/heroicons-outline-10.47.0.tgz", + "integrity": "sha512-C7bcJ/YPVKACoQLSBDM9DkgmrO1HmqAaru9kqYK2V+GaGUW5raf6dyTLo7GUYt/CJ7p3qWxsBz3SX6djzdaODQ==", + "requires": { + "@babel/runtime": "^7.20.7", + "@styled-icons/styled-icon": "^10.7.0" + } + }, + "@styled-icons/styled-icon": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/@styled-icons/styled-icon/-/styled-icon-10.7.0.tgz", + "integrity": "sha512-SCrhCfRyoY8DY7gUkpz+B0RqUg/n1Zaqrr2+YKmK/AyeNfCcoHuP4R9N4H0p/NA1l7PTU10ZkAWSLi68phnAjw==", + "requires": { + "@babel/runtime": "^7.19.0" + } + }, + "@swc/core": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.36.tgz", + "integrity": "sha512-Ogrd9uRNIj7nHjXxG66UlKBIcXESUenJ7OD6K2a8p82qlg6ne7Ne5Goiipm/heHYhSfVmjcnRWL9ZJ4gv+YCPA==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.3.36", + "@swc/core-darwin-x64": "1.3.36", + "@swc/core-linux-arm-gnueabihf": "1.3.36", + "@swc/core-linux-arm64-gnu": "1.3.36", + "@swc/core-linux-arm64-musl": "1.3.36", + "@swc/core-linux-x64-gnu": "1.3.36", + "@swc/core-linux-x64-musl": "1.3.36", + "@swc/core-win32-arm64-msvc": "1.3.36", + "@swc/core-win32-ia32-msvc": "1.3.36", + "@swc/core-win32-x64-msvc": "1.3.36" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.36.tgz", + "integrity": "sha512-lsP+C8p9cC/Vd9uAbtxpEnM8GoJI/MMnVuXak7OlxOtDH9/oTwmAcAQTfNGNaH19d2FAIRwf+5RbXCPnxa2Zjw==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.36.tgz", + "integrity": "sha512-jaLXsozWN5xachl9fPxDMi5nbWq1rRxPAt6ISeiYB6RJk0MQKH1634pOweBBem2pUDDzwDFXFw6f22LTm/cFvA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.36.tgz", + "integrity": "sha512-vcBdTHjoEpvJDbFlgto+S6VwAHzLA9GyCiuNcTU2v4KNQlFzhbO4A4PMfMCb/Z0RLJEr16tirfHdWIxjU3h8nw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.36.tgz", + "integrity": "sha512-o7f5OsvwWppJo+qIZmrGO5+XC6DPt6noecSbRHjF6o1YAcR13ETPC14k1eC9H1YbQwpyCFNVAFXyNcUbCeQyrQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.36.tgz", + "integrity": "sha512-FSHPngMi3c0fuGt9yY2Ubn5UcELi3EiPLJxBSC3X8TF9atI/WHZzK9PE9Gtn0C/LyRh4CoyOugDtSOPzGYmLQg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.36.tgz", + "integrity": "sha512-PHSsH2rek5pr3e0K09VgWAbrWK2vJhaI7MW9TPoTjyACYjcs3WwjcjQ30MghXUs2Dc/bXjWAOi9KFTjq/uCyFg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.36.tgz", + "integrity": "sha512-4LfMYQHzozHCKkIcmQy83b+4SpI+mOp6sYNbXqSRz5dYvTVjegKZXe596P1U/87cK2cgR4uYvkgkgBXquaWvwQ==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.36.tgz", + "integrity": "sha512-7y3dDcun79TAjCyk3Iv0eOMw1X/KNQbkVyKOGqnEgq9g22F8F1FoUGKHNTzUqVdzpHeJSsHgW5PlkEkl3c/d9w==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.36.tgz", + "integrity": "sha512-zK0VR3B4LX5hzQ+7eD+K+FkxJlJg5Lo36BeahMzQ+/i0IURpnuyFlW88sdkFkMsc2swdU6bpvxLZeIRQ3W4OUg==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.3.36", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.36.tgz", + "integrity": "sha512-2bIjr9DhAckGiXZEvj6z2z7ECPcTimG+wD0VuQTvr+wkx46uAJKl5Kq+Zk+dd15ErL7JGUtCet1T7bf1k4FwvQ==", + "dev": true, + "optional": true + }, + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "requires": { + "@types/ms": "*" + } + }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "requires": { + "@types/unist": "*" + } + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "requires": { + "@types/unist": "*" + } + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "devOptional": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "@types/vscode-webview": { + "version": "1.57.1", + "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz", + "integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ==" + }, + "@vitejs/plugin-react-swc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", + "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==", + "dev": true, + "requires": { + "@swc/core": "^1.3.35" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "requires": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, + "caniuse-lite": { + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + }, + "dependencies": { + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + } + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "requires": { + "character-entities": "^2.0.0" + } + }, + "defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true + }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "requires": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + } + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==" + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.311", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", + "integrity": "sha512-RoDlZufvrtr2Nx3Yx5MB8jX3aHIxm8nRWPJm3yVvyHmyKaRvn90RjzB6hNnt0AkhS3IInJdyRfQb4mWhPvUjVw==", + "dev": true + }, + "esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==" + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-from-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", + "integrity": "sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz", + "integrity": "sha512-tGvhT94e+cVnQt8JWE9/b3cUQZWS732TJxXHktvP+BYo62PpYD53Ls/6cC60rW21dW+txxiM4zMdc6abASvZKA==", + "requires": { + "@types/mdast": "^3.0.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==" + }, + "micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==" + }, + "micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==" + }, + "micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + } + }, + "postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "react-markdown": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz", + "integrity": "sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==", + "requires": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + } + }, + "react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + } + }, + "remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rollup": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.17.2.tgz", + "integrity": "sha512-qMNZdlQPCkWodrAZ3qnJtvCAl4vpQ8q77uEujVCCbC/6CLB7Lcmvjq7HyiOSnf4fxTT9XgsE36oLHJBH49xjqA==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "requires": { + "mri": "^1.1.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, + "style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tailwindcss": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", + "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "dev": true, + "requires": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.0.9", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==" + }, + "unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + } + }, + "unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, + "vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, + "vite": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.4.tgz", + "integrity": "sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==", + "dev": true, + "requires": { + "esbuild": "^0.16.14", + "fsevents": "~2.3.2", + "postcss": "^8.4.21", + "resolve": "^1.22.1", + "rollup": "^3.10.0" + } + }, + "vscode-webview": { + "version": "1.0.1-beta.1", + "resolved": "https://registry.npmjs.org/vscode-webview/-/vscode-webview-1.0.1-beta.1.tgz", + "integrity": "sha512-8H2icqlb93qIvZs7Me/0sALMqJrX2e2W7JVE0mfK8exUPETP9Bmg+feivaByd1Xbg78lcKaNF9ZRrIFSenUVxQ==", + "requires": { + "fs-extra": "^10.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } +} diff --git a/extension/react-app/package.json b/extension/react-app/package.json new file mode 100644 index 00000000..3993b030 --- /dev/null +++ b/extension/react-app/package.json @@ -0,0 +1,32 @@ +{ + "name": "react-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@styled-icons/heroicons-outline": "^10.47.0", + "@types/vscode-webview": "^1.57.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^8.0.5", + "react-redux": "^8.0.5", + "styled-components": "^5.3.6", + "vscode-webview": "^1.0.1-beta.1" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@types/styled-components": "^5.1.26", + "@vitejs/plugin-react-swc": "^3.0.0", + "autoprefixer": "^10.4.13", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7", + "typescript": "^4.9.3", + "vite": "^4.1.0" + } +} diff --git a/extension/react-app/postcss.config.cjs b/extension/react-app/postcss.config.cjs new file mode 100644 index 00000000..33ad091d --- /dev/null +++ b/extension/react-app/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/extension/react-app/public/continue_arrow.png b/extension/react-app/public/continue_arrow.png new file mode 100644 index 00000000..3b16ddf9 Binary files /dev/null and b/extension/react-app/public/continue_arrow.png differ diff --git a/extension/react-app/public/vite.svg b/extension/react-app/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/extension/react-app/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx new file mode 100644 index 00000000..0c40ced1 --- /dev/null +++ b/extension/react-app/src/App.tsx @@ -0,0 +1,29 @@ +import DebugPanel from "./components/DebugPanel"; +import MainTab from "./tabs/main"; +import { Provider } from "react-redux"; +import store from "./redux/store"; +import WelcomeTab from "./tabs/welcome"; +import ChatTab from "./tabs/chat"; +import Notebook from "./tabs/notebook"; + +function App() { + return ( + <> + + , + title: "Notebook", + }, + // { element: , title: "Debug Panel" }, + // { element: , title: "Welcome" }, + // { element: , title: "Chat" }, + ]} + > + + + ); +} + +export default App; diff --git a/extension/react-app/src/TestPage.tsx b/extension/react-app/src/TestPage.tsx new file mode 100644 index 00000000..d104980b --- /dev/null +++ b/extension/react-app/src/TestPage.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import styled from "styled-components"; + +const SideBySideDiv = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + grid-template-areas: "left right"; +`; + +const LeftDiv = styled.div` + grid-area: left; +`; + +const RightDiv = styled.div` + grid-area: right; +`; + +function TestPage() { + return ( +
+

Continue

+ + +

Left

+
+ +

Right

+
+
+
+ ); +} diff --git a/extension/react-app/src/assets/Hubot-Sans.woff2 b/extension/react-app/src/assets/Hubot-Sans.woff2 new file mode 100644 index 00000000..5089fc47 Binary files /dev/null and b/extension/react-app/src/assets/Hubot-Sans.woff2 differ diff --git a/extension/react-app/src/assets/Mona-Sans.woff2 b/extension/react-app/src/assets/Mona-Sans.woff2 new file mode 100644 index 00000000..8208a500 Binary files /dev/null and b/extension/react-app/src/assets/Mona-Sans.woff2 differ diff --git a/extension/react-app/src/assets/react.svg b/extension/react-app/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/extension/react-app/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx new file mode 100644 index 00000000..4c10ab23 --- /dev/null +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -0,0 +1,57 @@ +import hljs from "highlight.js"; +import { useEffect } from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, vscBackground } from "."; + +import { Clipboard } from "@styled-icons/heroicons-outline"; + +const StyledPre = styled.pre` + overflow: scroll; + border: 1px solid gray; + border-radius: ${defaultBorderRadius}; + background-color: ${vscBackground}; +`; + +const StyledCode = styled.code` + background-color: ${vscBackground}; +`; + +const StyledCopyButton = styled.button` + float: right; + border: none; + background-color: ${vscBackground}; + cursor: pointer; + padding: 0; + margin: 4px; + &:hover { + color: #fff; + } +`; + +function CopyButton(props: { textToCopy: string }) { + return ( + <> + { + navigator.clipboard.writeText(props.textToCopy); + }} + > + + + + ); +} + +function CodeBlock(props: { language?: string; children: string }) { + useEffect(() => { + hljs.highlightAll(); + }, [props.children]); + return ( + + + {props.children} + + ); +} + +export default CodeBlock; diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx new file mode 100644 index 00000000..626ae42f --- /dev/null +++ b/extension/react-app/src/components/CodeMultiselect.tsx @@ -0,0 +1,276 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { Button, buttonColor, defaultBorderRadius, secondaryDark } from "."; +import { useSelector } from "react-redux"; +import { + selectDebugContext, + selectAllRangesInFiles, + selectRangesMask, +} from "../redux/selectors/debugContextSelectors"; +import "../highlight/dark.min.css"; +import hljs from "highlight.js"; +import { postVscMessage } from "../vscode"; +import { RootStore } from "../redux/store"; +import { useDispatch } from "react-redux"; +import { + addRangeInFile, + deleteRangeInFileAt, + toggleSelectionAt, + updateFileSystem, +} from "../redux/slices/debugContexSlice"; +import { RangeInFile } from "../../../src/client"; +import { readRangeInVirtualFileSystem } from "../util"; + +//#region Styled Components + +const MultiSelectContainer = styled.div` + border-radius: ${defaultBorderRadius}; + padding: 4px; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const MultiSelectHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: left; + border-bottom: 1px solid gray; + padding-left: 4px; + padding-right: 4px; + & p { + overflow-wrap: break-word; + word-wrap: break-word; + -ms-wrap-flow: break-word; + overflow: hidden; + } +`; + +const MultiSelectOption = styled.div` + border-radius: ${defaultBorderRadius}; + padding-top: 4px; + cursor: pointer; + background-color: ${secondaryDark}; +`; + +const DeleteSelectedRangeButton = styled(Button)` + align-self: right; + padding: 0px; + margin-top: 0; + aspect-ratio: 1/1; + height: 28px; +`; + +const ToggleHighlightButton = styled(Button)` + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: 30px 1fr; + margin-left: 20px; + order: 1; + width: fit-content; +`; + +//#endregion + +//#region Path Formatting + +const filenameToLanguageMap: any = { + py: "python", + js: "javascript", + ts: "typescript", + html: "html", + css: "css", + java: "java", + c: "c", + cpp: "cpp", + cs: "csharp", + go: "go", + rb: "ruby", + rs: "rust", + swift: "swift", + php: "php", + scala: "scala", + kt: "kotlin", + dart: "dart", + hs: "haskell", + lua: "lua", + pl: "perl", + r: "r", + sql: "sql", + vb: "vb", + xml: "xml", + yaml: "yaml", +}; + +function filenameToLanguage(filename: string): string { + const extension = filename.split(".").pop(); + if (extension === undefined) { + return ""; + } + return filenameToLanguageMap[extension] || ""; +} + +function formatPathRelativeToWorkspace( + path: string, + workspacePath: string | undefined +) { + if (workspacePath === undefined) { + return path; + } + if (path.startsWith(workspacePath)) { + return path.substring(workspacePath.length + 1); + } else { + return path; + } +} + +function formatFileRange( + rangeInFile: RangeInFile, + workspacePath: string | undefined +) { + return `${formatPathRelativeToWorkspace( + rangeInFile.filepath, + workspacePath + )} (lines ${rangeInFile.range.start.line + 1}-${ + rangeInFile.range.end.line + 1 + })`; + // +1 because VSCode Ranges are 0-indexed +} + +//#endregion + +function CodeMultiselect(props: {}) { + // State + const [highlightLocked, setHighlightLocked] = useState(true); + + // Redux + const dispatch = useDispatch(); + const workspacePath = useSelector( + (state: RootStore) => state.config.workspacePath + ); + const debugContext = useSelector(selectDebugContext); + const rangesInFiles = useSelector(selectAllRangesInFiles); + const rangesInFilesMask = useSelector(selectRangesMask); + + useEffect(() => { + let eventListener = (event: any) => { + switch (event.data.type) { + case "highlightedCode": + if (!highlightLocked) { + dispatch( + addRangeInFile({ + rangeInFile: event.data.rangeInFile, + canUpdateLast: true, + }) + ); + dispatch(updateFileSystem(event.data.filesystem)); + } + break; + case "findSuspiciousCode": + for (let c of event.data.codeLocations) { + dispatch(addRangeInFile({ rangeInFile: c, canUpdateLast: false })); + } + dispatch(updateFileSystem(event.data.filesystem)); + postVscMessage("listTenThings", { debugContext }); + break; + } + }; + window.addEventListener("message", eventListener); + return () => window.removeEventListener("message", eventListener); + }, [debugContext, highlightLocked]); + + useEffect(() => { + hljs.highlightAll(); + }, [rangesInFiles]); + + return ( + + {rangesInFiles.map((range: RangeInFile, index: number) => { + return ( + { + dispatch(toggleSelectionAt(index)); + }} + > + +

+ {formatFileRange(range, workspacePath)} +

+ dispatch(deleteRangeInFileAt(index))} + > + x + +
+
+              
+                {readRangeInVirtualFileSystem(range, debugContext.filesystem)}
+              
+            
+
+ ); + })} + {rangesInFiles.length === 0 && ( + <> +

Highlight relevant code in the editor.

+ + )} + { + setHighlightLocked(!highlightLocked); + }} + > + {highlightLocked ? ( + <> + + + {" "} + Enable Highlight + + ) : ( + <> + + + {" "} + Disable Highlight + + )} + +
+ ); +} + +export default CodeMultiselect; diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx new file mode 100644 index 00000000..11dc7a92 --- /dev/null +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -0,0 +1,37 @@ +import styled, { keyframes } from "styled-components"; +import { Button } from "."; +import { Play } from "@styled-icons/heroicons-outline"; + +let StyledButton = styled(Button)` + margin: auto; + display: grid; + grid-template-columns: 30px 1fr; + align-items: center; + background: linear-gradient( + 95.23deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + + &:hover { + transition-delay: 0.5s; + transition-property: background; + background: linear-gradient( + 45deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + } +`; + +function ContinueButton(props: { onClick?: () => void }) { + return ( + + + {/* */} + Continue + + ); +} + +export default ContinueButton; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx new file mode 100644 index 00000000..ed00571b --- /dev/null +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -0,0 +1,121 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { postVscMessage } from "../vscode"; +import { useDispatch } from "react-redux"; +import { + setApiUrl, + setVscMachineId, + setSessionId, +} from "../redux/slices/configSlice"; +import { setHighlightedCode } from "../redux/slices/miscSlice"; +import { updateFileSystem } from "../redux/slices/debugContexSlice"; +import { buttonColor, defaultBorderRadius, vscBackground } from "."; +interface DebugPanelProps { + tabs: { + element: React.ReactElement; + title: string; + }[]; +} + +const GradientContainer = styled.div` + // Uncomment to get gradient border + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); + /* padding: 10px; */ + margin: 0; + height: 100%; + /* border: 1px solid white; */ + border-radius: ${defaultBorderRadius}; +`; + +const MainDiv = styled.div` + height: 100%; + border-radius: ${defaultBorderRadius}; + overflow: scroll; + scrollbar-base-color: transparent; + /* background: ${vscBackground}; */ + background-color: #1e1e1ede; +`; + +const TabBar = styled.div<{ numTabs: number }>` + display: grid; + grid-template-columns: repeat(${(props) => props.numTabs}, 1fr); +`; + +const TabsAndBodyDiv = styled.div` + display: grid; + grid-template-rows: auto 1fr; + height: 100%; +`; + +function DebugPanel(props: DebugPanelProps) { + const dispatch = useDispatch(); + useEffect(() => { + const eventListener = (event: any) => { + switch (event.data.type) { + case "onLoad": + dispatch(setApiUrl(event.data.apiUrl)); + dispatch(setVscMachineId(event.data.vscMachineId)); + dispatch(setSessionId(event.data.sessionId)); + break; + case "highlightedCode": + dispatch(setHighlightedCode(event.data.rangeInFile)); + dispatch(updateFileSystem(event.data.filesystem)); + break; + } + }; + window.addEventListener("message", eventListener); + postVscMessage("onLoad", {}); + return () => window.removeEventListener("message", eventListener); + }, []); + + const [currentTab, setCurrentTab] = useState(0); + + return ( + + + + {props.tabs.length > 1 && ( + + {props.tabs.map((tab, index) => { + return ( +
setCurrentTab(index)} + > + {tab.title} +
+ ); + })} +
+ )} + {props.tabs.map((tab, index) => { + return ( + + ); + })} +
+
+
+ ); +} + +export default DebugPanel; diff --git a/extension/react-app/src/components/IterationContainer.tsx b/extension/react-app/src/components/IterationContainer.tsx new file mode 100644 index 00000000..a0053519 --- /dev/null +++ b/extension/react-app/src/components/IterationContainer.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import styled from "styled-components"; +import { + defaultBorderRadius, + MainContainerWithBorder, + secondaryDark, + vscBackground, +} from "."; +import { RangeInFile, FileEdit } from "../../../src/client"; +import CodeBlock from "./CodeBlock"; +import SubContainer from "./SubContainer"; + +import { ChevronDown, ChevronRight } from "@styled-icons/heroicons-outline"; + +export interface IterationContext { + codeSelections: RangeInFile[]; + instruction: string; + suggestedChanges: FileEdit[]; + status: "waiting" | "accepted" | "rejected"; + summary?: string; + action: string; + error?: string; +} + +interface IterationContainerProps { + iterationContext: IterationContext; +} + +const IterationContainerDiv = styled.div<{ open: boolean }>` + background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; + border-radius: ${defaultBorderRadius}; + padding: ${(props) => (props.open ? "2px" : "8px")}; +`; + +function IterationContainer(props: IterationContainerProps) { + const [open, setOpen] = useState(false); + + return ( + + +

setOpen((prev) => !prev)} + > + {open ? : } + {props.iterationContext.summary || + props.iterationContext.codeSelections + .map((cs) => cs.filepath) + .join("\n")} +

+ + {open && ( + <> + + {props.iterationContext.action} + + {props.iterationContext.error && ( + + {props.iterationContext.error} + + )} + {props.iterationContext.suggestedChanges.map((sc) => { + return ( + + {sc.filepath} + {sc.replacement} + + ); + })} + + )} +
+
+ ); +} + +export default IterationContainer; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx new file mode 100644 index 00000000..03649b66 --- /dev/null +++ b/extension/react-app/src/components/StepContainer.tsx @@ -0,0 +1,208 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import styled, { keyframes } from "styled-components"; +import { + appear, + buttonColor, + defaultBorderRadius, + MainContainerWithBorder, + MainTextInput, + secondaryDark, + vscBackground, + GradientBorder, +} from "."; +import { RangeInFile, FileEdit } from "../../../src/client"; +import CodeBlock from "./CodeBlock"; +import SubContainer from "./SubContainer"; + +import { + ChevronDown, + ChevronRight, + Backward, +} from "@styled-icons/heroicons-outline"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import ReactMarkdown from "react-markdown"; +import ContinueButton from "./ContinueButton"; + +interface StepContainerProps { + historyNode: HistoryNode; + onReverse: () => void; + inFuture: boolean; + onRefinement: (input: string) => void; + onUserInput: (input: string) => void; +} + +const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` + opacity: ${(props) => (props.inFuture ? 0.3 : 1)}; + animation: ${appear} 0.3s ease-in-out; + /* padding-left: ${(props) => props.stepDepth * 20}px; */ + overflow: hidden; +`; + +const StepContainerDiv = styled.div<{ open: boolean }>` + background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; + border-radius: ${defaultBorderRadius}; + padding: 8px; +`; + +const HeaderDiv = styled.div` + display: grid; + grid-template-columns: 1fr auto; + align-items: center; +`; + +const HeaderButton = styled.button` + background-color: transparent; + border: 1px solid white; + border-radius: ${defaultBorderRadius}; + padding: 2px; + cursor: pointer; + color: white; + + &:hover { + background-color: white; + color: black; + } +`; + +const OnHoverDiv = styled.div` + text-align: center; + padding: 10px; + animation: ${appear} 0.3s ease-in-out; +`; + +const NaturalLanguageInput = styled(MainTextInput)` + width: 80%; +`; + +function StepContainer(props: StepContainerProps) { + const [open, setOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const naturalLanguageInputRef = useRef(null); + + useEffect(() => { + if (isHovered) { + naturalLanguageInputRef.current?.focus(); + } + }, [isHovered]); + + const onTextInput = useCallback(() => { + if (naturalLanguageInputRef.current) { + props.onRefinement(naturalLanguageInputRef.current.value); + naturalLanguageInputRef.current.value = ""; + } + }, [naturalLanguageInputRef]); + + return ( + { + setIsHovered(true); + }} + onMouseLeave={() => { + setIsHovered(false); + }} + hidden={props.historyNode.step.hide as any} + > + setOpen((prev) => !prev)} + > + + +

+ {open ? ( + + ) : ( + + )} + {props.historyNode.step.name as any}: +

+ { + e.stopPropagation(); + props.onReverse(); + }} + > + + +
+ + + {props.historyNode.step.description as any} + + + {props.historyNode.step.name === "Waiting for user input" && ( + { + if (e.key === "Enter") { + props.onUserInput(e.currentTarget.value); + } + }} + type="text" + onSubmit={(ev) => { + props.onUserInput(ev.currentTarget.value); + }} + /> + )} + {props.historyNode.step.name === "Waiting for user confirmation" && ( + <> + + { + props.onUserInput("ok"); + e.preventDefault(); + e.stopPropagation(); + }} + type="button" + value="Confirm" + /> + + )} + + {open && ( + <> + {/* {props.historyNode.observation && ( + + Error Here + + )} */} + {/* {props.iterationContext.suggestedChanges.map((sc) => { + return ( + + {sc.filepath} + {sc.replacement} + + ); + })} */} + + )} +
+
+ + +
+ ); +} + +export default StepContainer; diff --git a/extension/react-app/src/components/SubContainer.tsx b/extension/react-app/src/components/SubContainer.tsx new file mode 100644 index 00000000..87e2094c --- /dev/null +++ b/extension/react-app/src/components/SubContainer.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, secondaryDark, appear } from "."; + +const SubContainerDiv = styled.div` + margin: 4px; + padding: 8px; + border-radius: ${defaultBorderRadius}; + background-color: ${secondaryDark}; + + animation: ${appear} 0.3s ease-in-out; +`; + +function SubContainer(props: { children: React.ReactNode; title: string }) { + return ( + + {props.title} +

+ {props.children} +
+ ); +} + +export default SubContainer; diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx new file mode 100644 index 00000000..6219654d --- /dev/null +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { postVscMessage } from "../vscode"; + +function VSCodeFileLink(props: { path: string; text?: string }) { + return ( + { + postVscMessage("openFile", { path: props.path }); + }} + > + {props.text || props.path} + + ); +} + +export default VSCodeFileLink; diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts new file mode 100644 index 00000000..e37c97f3 --- /dev/null +++ b/extension/react-app/src/components/index.ts @@ -0,0 +1,136 @@ +import styled, { keyframes } from "styled-components"; + +export const defaultBorderRadius = "5px"; +export const secondaryDark = "rgb(37 37 38)"; +export const vscBackground = "rgb(30 30 30)"; +export const buttonColor = "rgb(113 28 59)"; +export const buttonColorHover = "rgb(113 28 59 0.67)"; + +export const Button = styled.button` + padding: 10px 12px; + margin: 8px 0; + border-radius: ${defaultBorderRadius}; + cursor: pointer; + + border: none; + color: white; + background-color: ${buttonColor}; + + &:disabled { + color: gray; + } + + &:hover:enabled { + background-color: ${buttonColorHover}; + } +`; + +export const TextArea = styled.textarea` + width: 100%; + border-radius: ${defaultBorderRadius}; + border: none; + background-color: ${secondaryDark}; + resize: vertical; + + padding: 4px; + caret-color: white; + color: white; + + &:focus { + outline: 1px solid ${buttonColor}; + } +`; + +export const Pre = styled.pre` + border-radius: ${defaultBorderRadius}; + padding: 8px; + max-height: 150px; + overflow: scroll; + margin: 0; + background-color: ${secondaryDark}; + border: none; + + /* text wrapping */ + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +`; + +export const H3 = styled.h3` + background-color: ${secondaryDark}; + border-radius: ${defaultBorderRadius}; + padding: 4px 8px; + width: fit-content; +`; + +export const TextInput = styled.input.attrs({ type: "text" })` + width: 100%; + padding: 12px 20px; + margin: 8px 0; + box-sizing: border-box; + border-radius: ${defaultBorderRadius}; + border: 2px solid gray; +`; + +const spin = keyframes` + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +`; + +export const Loader = styled.div` + border: 4px solid transparent; + border-radius: 50%; + border-top: 4px solid white; + width: 36px; + height: 36px; + -webkit-animation: ${spin} 1s ease-in-out infinite; + animation: ${spin} 1s ease-in-out infinite; + margin: auto; +`; + +export const GradientBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); +`; + +export const MainContainerWithBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background-color: white; +`; + +export const MainTextInput = styled.textarea` + padding: 8px; + font-size: 16px; + border-radius: ${defaultBorderRadius}; + border: 1px solid #ccc; + margin: 8px 8px; + background-color: ${vscBackground}; + color: white; + outline: 1px solid orange; + resize: none; +`; + +export const appear = keyframes` + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0px); + } +`; diff --git a/extension/react-app/src/continue_arrow.png b/extension/react-app/src/continue_arrow.png new file mode 100644 index 00000000..3b16ddf9 Binary files /dev/null and b/extension/react-app/src/continue_arrow.png differ diff --git a/extension/react-app/src/highlight/dark.min.css b/extension/react-app/src/highlight/dark.min.css new file mode 100644 index 00000000..9268d7c9 --- /dev/null +++ b/extension/react-app/src/highlight/dark.min.css @@ -0,0 +1,53 @@ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; +} +code.hljs { + padding: 3px 5px; +} +.hljs { + color: #ddd; + background: #252526; +} +.hljs-keyword, +.hljs-link, +.hljs-literal, +.hljs-section, +.hljs-selector-tag { + color: #fff; +} +.hljs-addition, +.hljs-attribute, +.hljs-built_in, +.hljs-bullet, +.hljs-name, +.hljs-string, +.hljs-symbol, +.hljs-template-tag, +.hljs-template-variable, +.hljs-title, +.hljs-type, +.hljs-variable { + color: #d88; +} +.hljs-comment, +.hljs-deletion, +.hljs-meta, +.hljs-quote { + color: #979797; +} +.hljs-doctag, +.hljs-keyword, +.hljs-literal, +.hljs-name, +.hljs-section, +.hljs-selector-tag, +.hljs-strong, +.hljs-title, +.hljs-type { + font-weight: 700; +} +.hljs-emphasis { + font-style: italic; +} diff --git a/extension/react-app/src/hooks/useArrayState.ts b/extension/react-app/src/hooks/useArrayState.ts new file mode 100644 index 00000000..d379e720 --- /dev/null +++ b/extension/react-app/src/hooks/useArrayState.ts @@ -0,0 +1,29 @@ +import { useState } from "react"; + +function useArrayState(initialValue: T[]) { + const [value, setValue] = useState(initialValue); + + function add(item: any) { + setValue((prev) => [...prev, item]); + } + + function remove(index: number) { + setValue((prev) => prev.filter((_, i) => i !== index)); + } + + function edit(editFn: (prev: T[]) => T[]) { + setValue((prev) => editFn(prev)); + } + + function replace(atIndex: number, withItem: T) { + setValue((prev) => { + let updated = [...prev]; + updated[atIndex] = withItem; + return updated; + }); + } + + return { value, add, remove, edit, replace }; +} + +export default useArrayState; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts new file mode 100644 index 00000000..147172bd --- /dev/null +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from "react"; +import { RootStore } from "../redux/store"; +import { useSelector } from "react-redux"; + +function useContinueWebsocket( + serverUrl: string, + onMessage: (message: { data: any }) => void +) { + const sessionId = useSelector((state: RootStore) => state.config.sessionId); + const [websocket, setWebsocket] = useState(undefined); + + async function connect() { + while (!sessionId) { + await new Promise((resolve) => setTimeout(resolve, 300)); + } + + console.log("Creating websocket", sessionId); + + const wsUrl = + serverUrl.replace("http", "ws") + + "/notebook/ws?session_id=" + + encodeURIComponent(sessionId); + + const ws = new WebSocket(wsUrl); + setWebsocket(ws); + + // Set up callbacks + ws.onopen = () => { + console.log("Websocket opened"); + ws.send(JSON.stringify({ sessionId })); + }; + + ws.onmessage = (msg) => { + onMessage(msg); + console.log("Got message", msg); + }; + + ws.onclose = (msg) => { + console.log("Websocket closed"); + setWebsocket(undefined); + }; + + return ws; + } + + async function getConnection() { + if (!websocket) { + return await connect(); + } + return websocket; + } + + async function send(message: object) { + let ws = await getConnection(); + ws.send(JSON.stringify(message)); + } + + useEffect(() => { + if (!sessionId) { + return; + } + connect(); + }, [sessionId]); + + return { send }; +} +export default useContinueWebsocket; diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css new file mode 100644 index 00000000..dd38eec3 --- /dev/null +++ b/extension/react-app/src/index.css @@ -0,0 +1,34 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --secondary-dark: 37 37 38; + --vsc-background: 30 30 30; + --button-color: rgb(113, 28, 59); + --button-color-hover: rgba(113, 28, 59, 0.667); + --def-border-radius: 5px; +} + +@font-face { + font-family: "Mona Sans"; + src: url("assets/Mona-Sans.woff2") format("woff2 supports variations"), + url("assets/Mona-Sans.woff2") format("woff2-variations"); + font-weight: 200 900; + font-stretch: 75% 85%; +} + +html, +body, +#root { + height: calc(100% - 7px); +} + +body { + background-color: var(--vsc-background); + padding: 0; + color: white; + font-family: "Mona Sans", "Arial", sans-serif; + padding: 0px; + margin: 0px; +} diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx new file mode 100644 index 00000000..791f139e --- /dev/null +++ b/extension/react-app/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/extension/react-app/src/redux/hooks.ts b/extension/react-app/src/redux/hooks.ts new file mode 100644 index 00000000..a6aef869 --- /dev/null +++ b/extension/react-app/src/redux/hooks.ts @@ -0,0 +1,21 @@ +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { RootStore } from "./store"; +import { selectDebugContextValue } from "./selectors/debugContextSelectors"; +import { updateValue } from "./slices/debugContexSlice"; +import { SerializedDebugContext } from "../../../src/client"; + +export function useDebugContextValue( + key: keyof SerializedDebugContext, + defaultValue: any +): [any, (value: any) => void] { + const dispatch = useDispatch(); + const state = + useSelector((state: RootStore) => selectDebugContextValue(state, key)) || + defaultValue; + const boundAction = useCallback( + (value: any) => dispatch(updateValue({ key, value })), + [dispatch, key] + ); + return [state, boundAction]; +} diff --git a/extension/react-app/src/redux/selectors/chatSelectors.ts b/extension/react-app/src/redux/selectors/chatSelectors.ts new file mode 100644 index 00000000..51e8a636 --- /dev/null +++ b/extension/react-app/src/redux/selectors/chatSelectors.ts @@ -0,0 +1,11 @@ +import { RootStore } from "../store"; + +const selectChatMessages = (state: RootStore) => { + return state.chat.messages; +}; + +const selectIsStreaming = (state: RootStore) => { + return state.chat.isStreaming; +}; + +export { selectChatMessages, selectIsStreaming }; diff --git a/extension/react-app/src/redux/selectors/debugContextSelectors.ts b/extension/react-app/src/redux/selectors/debugContextSelectors.ts new file mode 100644 index 00000000..89201bb7 --- /dev/null +++ b/extension/react-app/src/redux/selectors/debugContextSelectors.ts @@ -0,0 +1,29 @@ +import { RootStore } from "../store"; + +const selectDebugContext = (state: RootStore) => { + return { + ...state.debugState.debugContext, + rangesInFiles: state.debugState.debugContext.rangesInFiles.filter( + (_, index) => state.debugState.rangesMask[index] + ), + }; +}; + +const selectAllRangesInFiles = (state: RootStore) => { + return state.debugState.debugContext.rangesInFiles; +}; + +const selectRangesMask = (state: RootStore) => { + return state.debugState.rangesMask; +}; + +const selectDebugContextValue = (state: RootStore, key: string) => { + return (state.debugState.debugContext as any)[key]; +}; + +export { + selectDebugContext, + selectDebugContextValue, + selectAllRangesInFiles, + selectRangesMask, +}; diff --git a/extension/react-app/src/redux/selectors/miscSelectors.ts b/extension/react-app/src/redux/selectors/miscSelectors.ts new file mode 100644 index 00000000..7dbaed09 --- /dev/null +++ b/extension/react-app/src/redux/selectors/miscSelectors.ts @@ -0,0 +1,5 @@ +import { RootStore } from "../store"; + +const selectHighlightedCode = (state: RootStore) => state.misc.highlightedCode; + +export { selectHighlightedCode }; diff --git a/extension/react-app/src/redux/slices/chatSlice.ts b/extension/react-app/src/redux/slices/chatSlice.ts new file mode 100644 index 00000000..848cccd4 --- /dev/null +++ b/extension/react-app/src/redux/slices/chatSlice.ts @@ -0,0 +1,93 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { ChatMessage, RootStore } from "../store"; + +export const chatSlice = createSlice({ + name: "chat", + initialState: { + messages: [], + isStreaming: false, + } as RootStore["chat"], + reducers: { + addMessage: ( + state, + action: { + type: string; + payload: ChatMessage; + } + ) => { + return { + ...state, + messages: [...state.messages, action.payload], + }; + }, + setIsStreaming: (state, action) => { + return { + ...state, + isStreaming: action.payload, + }; + }, + streamUpdate: (state, action) => { + if (!state.isStreaming) { + return { + ...state, + messages: [ + ...state.messages, + { + role: "assistant", + content: action.payload, + }, + ], + isStreaming: true, + }; + } else { + let lastMessage = state.messages[state.messages.length - 1]; + if (lastMessage.role !== "assistant") { + return { + ...state, + messages: [ + ...state.messages, + { + role: "assistant", + content: action.payload, + }, + ], + isStreaming: true, + }; + } + return { + ...state, + messages: [ + ...state.messages.slice(0, state.messages.length - 1), + { + ...lastMessage, + content: lastMessage.content + action.payload, + }, + ], + isStreaming: true, + }; + } + }, + closeStream: (state) => { + return { + ...state, + isStreaming: false, + }; + }, + clearChat: (state) => { + return { + ...state, + messages: [], + isStreaming: false, + }; + }, + }, +}); + +export const { + addMessage, + streamUpdate, + closeStream, + clearChat, + setIsStreaming, +} = chatSlice.actions; +export default chatSlice.reducer; diff --git a/extension/react-app/src/redux/slices/configSlice.ts b/extension/react-app/src/redux/slices/configSlice.ts new file mode 100644 index 00000000..a6a641e6 --- /dev/null +++ b/extension/react-app/src/redux/slices/configSlice.ts @@ -0,0 +1,45 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RootStore } from "../store"; + +export const configSlice = createSlice({ + name: "config", + initialState: { + apiUrl: "http://localhost:8000", + } as RootStore["config"], + reducers: { + setWorkspacePath: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => { + return { + ...state, + workspacePath: action.payload, + }; + }, + setApiUrl: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => ({ + ...state, + apiUrl: action.payload, + }), + setVscMachineId: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => ({ + ...state, + vscMachineId: action.payload, + }), + setSessionId: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => ({ + ...state, + sessionId: action.payload, + }), + }, +}); + +export const { setVscMachineId, setApiUrl, setWorkspacePath, setSessionId } = + configSlice.actions; +export default configSlice.reducer; diff --git a/extension/react-app/src/redux/slices/debugContexSlice.ts b/extension/react-app/src/redux/slices/debugContexSlice.ts new file mode 100644 index 00000000..647440d5 --- /dev/null +++ b/extension/react-app/src/redux/slices/debugContexSlice.ts @@ -0,0 +1,149 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RangeInFile, SerializedDebugContext } from "../../../../src/client"; +import { RootStore } from "../store"; + +export const debugStateSlice = createSlice({ + name: "debugState", + initialState: { + debugContext: { + rangesInFiles: [], + filesystem: {}, + traceback: undefined, + description: undefined, + }, + rangesMask: [], + } as RootStore["debugState"], + reducers: { + updateValue: ( + state: RootStore["debugState"], + action: { + type: string; + payload: { key: keyof SerializedDebugContext; value: any }; + } + ) => { + return { + ...state, + debugContext: { + ...state.debugContext, + [action.payload.key]: action.payload.value, + }, + }; + }, + addRangeInFile: ( + state: RootStore["debugState"], + action: { + type: string; + payload: { + rangeInFile: RangeInFile; + canUpdateLast: boolean; + }; + } + ) => { + let rangesInFiles = state.debugContext.rangesInFiles; + // If identical to existing range, don't add. Ideally you check for overlap of ranges. + for (let range of rangesInFiles) { + if ( + range.filepath === action.payload.rangeInFile.filepath && + range.range.start.line === + action.payload.rangeInFile.range.start.line && + range.range.end.line === action.payload.rangeInFile.range.end.line + ) { + return state; + } + } + + if ( + action.payload.canUpdateLast && + rangesInFiles.length > 0 && + rangesInFiles[rangesInFiles.length - 1].filepath === + action.payload.rangeInFile.filepath + ) { + return { + ...state, + debugContext: { + ...state.debugContext, + rangesInFiles: [ + ...rangesInFiles.slice(0, rangesInFiles.length - 1), + action.payload.rangeInFile, + ], + }, + }; + } else { + return { + ...state, + debugContext: { + ...state.debugContext, + rangesInFiles: [ + ...state.debugContext.rangesInFiles, + action.payload.rangeInFile, + ], + }, + rangesMask: [...state.rangesMask, true], + }; + } + }, + deleteRangeInFileAt: ( + state: RootStore["debugState"], + action: { + type: string; + payload: number; + } + ) => { + return { + ...state, + debugContext: { + ...state.debugContext, + rangesInFiles: state.debugContext.rangesInFiles.filter( + (_, index) => index !== action.payload + ), + }, + rangesMask: state.rangesMask.filter( + (_, index) => index !== action.payload + ), + }; + }, + toggleSelectionAt: ( + state: RootStore["debugState"], + action: { + type: string; + payload: number; + } + ) => { + return { + ...state, + rangesMask: state.rangesMask.map((_, index) => + index === action.payload + ? !state.rangesMask[index] + : state.rangesMask[index] + ), + }; + }, + updateFileSystem: ( + state: RootStore["debugState"], + action: { + type: string; + payload: { [filepath: string]: string }; + } + ) => { + return { + ...state, + debugContext: { + ...state.debugContext, + filesystem: { + ...state.debugContext.filesystem, + ...action.payload, + }, + }, + }; + }, + }, +}); + +export const { + updateValue, + updateFileSystem, + addRangeInFile, + deleteRangeInFileAt, + toggleSelectionAt, +} = debugStateSlice.actions; +export default debugStateSlice.reducer; diff --git a/extension/react-app/src/redux/slices/miscSlice.ts b/extension/react-app/src/redux/slices/miscSlice.ts new file mode 100644 index 00000000..c59cc4eb --- /dev/null +++ b/extension/react-app/src/redux/slices/miscSlice.ts @@ -0,0 +1,16 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export const miscSlice = createSlice({ + name: "misc", + initialState: { + highlightedCode: "", + }, + reducers: { + setHighlightedCode: (state, action) => { + state.highlightedCode = action.payload; + }, + }, +}); + +export const { setHighlightedCode } = miscSlice.actions; +export default miscSlice.reducer; diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts new file mode 100644 index 00000000..f9eb0517 --- /dev/null +++ b/extension/react-app/src/redux/store.ts @@ -0,0 +1,43 @@ +import { configureStore } from "@reduxjs/toolkit"; +import debugStateReducer from "./slices/debugContexSlice"; +import chatReducer from "./slices/chatSlice"; +import configReducer from "./slices/configSlice"; +import miscReducer from "./slices/miscSlice"; +import { RangeInFile, SerializedDebugContext } from "../../../src/client"; + +export interface ChatMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export interface RootStore { + debugState: { + debugContext: SerializedDebugContext; + rangesMask: boolean[]; + }; + config: { + workspacePath: string | undefined; + apiUrl: string; + vscMachineId: string | undefined; + sessionId: string | undefined; + sessionStarted: number | undefined; + }; + chat: { + messages: ChatMessage[]; + isStreaming: boolean; + }; + misc: { + highlightedCode: RangeInFile | undefined; + }; +} + +const store = configureStore({ + reducer: { + debugState: debugStateReducer, + chat: chatReducer, + config: configReducer, + misc: miscReducer, + }, +}); + +export default store; diff --git a/extension/react-app/src/tabs/additionalContext.tsx b/extension/react-app/src/tabs/additionalContext.tsx new file mode 100644 index 00000000..98fce9f1 --- /dev/null +++ b/extension/react-app/src/tabs/additionalContext.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { H3, TextArea } from "../components"; + +function AdditionalContextTab() { + return ( +
+

Additional Context

+ +

+
+ ); +} + +export default AdditionalContextTab; diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx new file mode 100644 index 00000000..ab632220 --- /dev/null +++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx @@ -0,0 +1,73 @@ +import React, { useEffect } from "react"; +import { ChatMessage } from "../../redux/store"; +import styled from "styled-components"; +import { + buttonColor, + defaultBorderRadius, + secondaryDark, +} from "../../components"; +import VSCodeFileLink from "../../components/VSCodeFileLink"; +import ReactMarkdown from "react-markdown"; +import "../../highlight/dark.min.css"; +import hljs from "highlight.js"; +import { useSelector } from "react-redux"; +import { selectIsStreaming } from "../../redux/selectors/chatSelectors"; + +const Container = styled.div` + padding-left: 8px; + padding-right: 8px; + border-radius: 8px; + margin: 3px; + width: fit-content; + max-width: 75%; + overflow: scroll; + word-wrap: break-word; + -ms-word-wrap: break-word; + height: fit-content; + overflow: hidden; + background-color: ${(props) => { + if (props.role === "user") { + return buttonColor; + } else { + return secondaryDark; + } + }}; + float: ${(props) => { + if (props.role === "user") { + return "right"; + } else { + return "left"; + } + }}; + display: block; + + & pre { + border: 1px solid gray; + border-radius: ${defaultBorderRadius}; + } +`; + +function MessageDiv(props: ChatMessage) { + const [richContent, setRichContent] = React.useState([]); + const isStreaming = useSelector(selectIsStreaming); + + useEffect(() => { + if (!isStreaming) { + hljs.highlightAll(); + } + }, [richContent, isStreaming]); + + useEffect(() => { + setRichContent([{props.content}]); + }, [props.content]); + + return ( + <> +
+ {richContent} +
+ + ); +} + +export default MessageDiv; diff --git a/extension/react-app/src/tabs/chat/index.tsx b/extension/react-app/src/tabs/chat/index.tsx new file mode 100644 index 00000000..a93ad4f9 --- /dev/null +++ b/extension/react-app/src/tabs/chat/index.tsx @@ -0,0 +1,267 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { selectChatMessages } from "../../redux/selectors/chatSelectors"; +import MessageDiv from "./MessageDiv"; +import styled from "styled-components"; +import { addMessage, setIsStreaming } from "../../redux/slices/chatSlice"; +import { AnyAction, Dispatch } from "@reduxjs/toolkit"; +import { closeStream, streamUpdate } from "../../redux/slices/chatSlice"; +import { ChatMessage, RootStore } from "../../redux/store"; +import { postVscMessage, vscRequest } from "../../vscode"; +import { defaultBorderRadius, Loader } from "../../components"; +import { selectHighlightedCode } from "../../redux/selectors/miscSelectors"; +import { readRangeInVirtualFileSystem } from "../../util"; +import { selectDebugContext } from "../../redux/selectors/debugContextSelectors"; + +let textEntryBarHeight = "30px"; + +const ChatContainer = styled.div` + display: grid; + grid-template-rows: 1fr auto; + height: 100%; +`; + +const BottomDiv = styled.div` + display: grid; + grid-template-rows: auto auto; +`; + +const BottomButton = styled.button( + (props: { active: boolean }) => ` + font-size: 10px; + border: none; + color: white; + margin-right: 4px; + cursor: pointer; + background-color: ${props.active ? "black" : "gray"}; + border-radius: ${defaultBorderRadius}; + padding: 8px; +` +); + +const TextEntryBar = styled.input` + height: ${textEntryBarHeight}; + border-bottom-left-radius: ${defaultBorderRadius}; + border-bottom-right-radius: ${defaultBorderRadius}; + padding: 8px; + border: 1px solid white; + background-color: black; + color: white; + outline: none; +`; + +function ChatTab() { + const dispatch = useDispatch(); + const chatMessages = useSelector(selectChatMessages); + const isStreaming = useSelector((state: RootStore) => state.chat.isStreaming); + const baseUrl = useSelector((state: RootStore) => state.config.apiUrl); + const debugContext = useSelector(selectDebugContext); + + const [includeHighlightedCode, setIncludeHighlightedCode] = useState(true); + const [writeToEditor, setWriteToEditor] = useState(false); + const [waitingForResponse, setWaitingForResponse] = useState(false); + + const highlightedCode = useSelector(selectHighlightedCode); + + const streamToStateThunk = useCallback( + (dispatch: Dispatch, getResponse: () => Promise) => { + let streamToCursor = writeToEditor; + getResponse().then((resp) => { + setWaitingForResponse(false); + if (resp.body) { + resp.body.pipeTo( + new WritableStream({ + write(chunk) { + let update = new TextDecoder("utf-8").decode(chunk); + dispatch(streamUpdate(update)); + if (streamToCursor) { + postVscMessage("streamUpdate", { update }); + } + }, + close() { + dispatch(closeStream()); + if (streamToCursor) { + postVscMessage("closeStream", null); + } + }, + }) + ); + } + }); + }, + [writeToEditor] + ); + + const compileHiddenChatMessages = useCallback(async () => { + let messages: ChatMessage[] = []; + if ( + includeHighlightedCode && + highlightedCode?.filepath !== undefined && + highlightedCode?.range !== undefined && + debugContext.filesystem[highlightedCode.filepath] !== undefined + ) { + let fileContents = readRangeInVirtualFileSystem( + highlightedCode, + debugContext.filesystem + ); + if (fileContents) { + messages.push({ + role: "user", + content: fileContents, + }); + } + } else { + // Similarity search over workspace + let data = await vscRequest("queryEmbeddings", { + query: chatMessages[chatMessages.length - 1].content, + }); + let codeContextMessages = data.results.map( + (result: { id: string; document: string }) => { + let msg: ChatMessage = { + role: "user", + content: `File: ${result.id} \n ${result.document}`, + }; + return msg; + } + ); + codeContextMessages.push({ + role: "user", + content: + "Use the above code to help you answer the question below. Answer in asterisk bullet points, and give the full path whenever you reference files.", + }); + messages.push(...codeContextMessages); + } + + let systemMsgContent = writeToEditor + ? "Respond only with the exact code requested, no additional text." + : "Use the above code to help you answer the question below. Respond in markdown if using bullets or other special formatting, being sure to specify language for code blocks."; + + messages.push({ + role: "system", + content: systemMsgContent, + }); + return messages; + }, [highlightedCode, chatMessages, includeHighlightedCode, writeToEditor]); + + useEffect(() => { + if ( + chatMessages.length > 0 && + chatMessages[chatMessages.length - 1].role === "user" && + !isStreaming + ) { + dispatch(setIsStreaming(true)); + streamToStateThunk(dispatch, async () => { + if (chatMessages.length === 0) { + return new Promise((resolve, _) => resolve(new Response())); + } + let hiddenChatMessages = await compileHiddenChatMessages(); + let augmentedMessages = [ + ...chatMessages.slice(0, -1), + ...hiddenChatMessages, + chatMessages[chatMessages.length - 1], + ]; + console.log(augmentedMessages); + // The autogenerated client can't handle streams, so have to go raw + return fetch(`${baseUrl}/chat/complete`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + messages: augmentedMessages, + }), + }); + }); + } + }, [chatMessages, dispatch, isStreaming, highlightedCode]); + + const chatMessagesDiv = useRef(null); + useEffect(() => { + // Scroll to bottom + let interval = setInterval(() => { + if (chatMessagesDiv.current && !waitingForResponse) { + chatMessagesDiv.current.scrollTop += Math.max( + 4, + 0.05 * chatMessagesDiv.current.scrollHeight - + chatMessagesDiv.current.clientHeight - + chatMessagesDiv.current.scrollTop + ); + if ( + chatMessagesDiv.current.scrollTop >= + chatMessagesDiv.current.scrollHeight - + chatMessagesDiv.current.clientHeight + ) { + clearInterval(interval); + } + } + }, 10); + }, [chatMessages, chatMessagesDiv, waitingForResponse]); + + return ( + +
+

Chat

+
+
+ {chatMessages.length > 0 ? ( + chatMessages.map((message, idx) => { + return ; + }) + ) : ( +

+ You can ask questions about your codebase or ask for code written + directly in the editor. +

+ )} + {waitingForResponse && } +
+
+ + +
+
+ {/*

+ Highlighted code is automatically included in your chat message. +

*/} + { + setWriteToEditor(!writeToEditor); + }} + > + {writeToEditor ? "Writing to editor" : "Write to editor"} + + + { + setIncludeHighlightedCode(!includeHighlightedCode); + }} + > + {includeHighlightedCode + ? "Including highlighted code" + : "Automatically finding relevant code"} + +
+
+ { + if (e.key === "Enter" && e.currentTarget.value !== "") { + console.log("Sending message", e.currentTarget.value); + dispatch( + addMessage({ content: e.currentTarget.value, role: "user" }) + ); + (e.target as any).value = ""; + setWaitingForResponse(true); + } + }} + > +
+
+ ); +} + +export default ChatTab; diff --git a/extension/react-app/src/tabs/main.tsx b/extension/react-app/src/tabs/main.tsx new file mode 100644 index 00000000..a8b3300d --- /dev/null +++ b/extension/react-app/src/tabs/main.tsx @@ -0,0 +1,189 @@ +import React, { useEffect, useState } from "react"; +import { H3, TextArea, Button, Pre, Loader } from "../components"; +import styled from "styled-components"; +import { postVscMessage, withProgress } from "../vscode"; +import { useDebugContextValue } from "../redux/hooks"; +import CodeMultiselect from "../components/CodeMultiselect"; +import { useSelector } from "react-redux"; +import { selectDebugContext } from "../redux/selectors/debugContextSelectors"; +import { useDispatch } from "react-redux"; +import { updateValue } from "../redux/slices/debugContexSlice"; +import { setWorkspacePath } from "../redux/slices/configSlice"; +import { SerializedDebugContext } from "../../../src/client"; +import { useEditCache } from "../util/editCache"; +import { useApi } from "../util/api"; + +const ButtonDiv = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + gap: 4px; + margin: 4px; + flex-wrap: wrap; + + & button { + flex-grow: 1; + } +`; + +function MainTab(props: any) { + const dispatch = useDispatch(); + + const [suggestion, setSuggestion] = useState(""); + const [traceback, setTraceback] = useDebugContextValue("traceback", ""); + const [selectedRanges, setSelectedRanges] = useDebugContextValue( + "rangesInFiles", + [] + ); + + const editCache = useEditCache(); + const { debugApi } = useApi(); + + const [responseLoading, setResponseLoading] = useState(false); + + let debugContext = useSelector(selectDebugContext); + + useEffect(() => { + editCache.preloadEdit(debugContext); + }, [debugContext]); + + function postVscMessageWithDebugContext( + type: string, + overrideDebugContext: SerializedDebugContext | null = null + ) { + postVscMessage(type, { + debugContext: overrideDebugContext || debugContext, + }); + } + + function launchFindSuspiciousCode(newTraceback: string) { + // setTraceback's effects don't occur immediately, so we have to add it to the debug context manually + let updatedDebugContext = { + ...debugContext, + traceback: newTraceback, + }; + postVscMessageWithDebugContext("findSuspiciousCode", updatedDebugContext); + postVscMessageWithDebugContext("preloadEdit", updatedDebugContext); + } + + useEffect(() => { + const eventListener = (event: any) => { + switch (event.data.type) { + case "suggestFix": + case "explainCode": + case "listTenThings": + setSuggestion(event.data.value); + setResponseLoading(false); + break; + case "traceback": + setTraceback(event.data.value); + launchFindSuspiciousCode(event.data.value); + break; + case "workspacePath": + dispatch(setWorkspacePath(event.data.value)); + break; + } + }; + window.addEventListener("message", eventListener); + + return () => window.removeEventListener("message", eventListener); + }, [debugContext, selectedRanges]); + + return ( +
+

Debug Panel

+ +

Code Sections

+ + +

Bug Description

+ + +

Stack Trace

+ + + + + + + + + + + + + + +

+
+ ); +} + +export default MainTab; diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx new file mode 100644 index 00000000..a9c69c5b --- /dev/null +++ b/extension/react-app/src/tabs/notebook.tsx @@ -0,0 +1,285 @@ +import styled from "styled-components"; +import { + Button, + defaultBorderRadius, + vscBackground, + MainTextInput, + Loader, +} from "../components"; +import ContinueButton from "../components/ContinueButton"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { History } from "../../../schema/History"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import StepContainer from "../components/StepContainer"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; +import useContinueWebsocket from "../hooks/useWebsocket"; + +let TopNotebookDiv = styled.div` + display: grid; + grid-template-columns: 1fr; +`; + +let UserInputQueueItem = styled.div` + border-radius: ${defaultBorderRadius}; + color: gray; + padding: 8px; + margin: 8px; + text-align: center; +`; + +interface NotebookProps { + firstObservation?: any; +} + +function Notebook(props: NotebookProps) { + const serverUrl = useSelector((state: RootStore) => state.config.apiUrl); + + const [waitingForSteps, setWaitingForSteps] = useState(false); + const [userInputQueue, setUserInputQueue] = useState([]); + const [history, setHistory] = useState(); + // { + // timeline: [ + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [ + // { + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // }, + // null, + // ], + // }, + // { + // step: { + // name: "EditCodeStep", + // range_in_files: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // }, + // ], + // prompt: + // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", + // description: + // "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // }, + // output: [ + // null, + // { + // reversible: true, + // actions: [ + // { + // reversible: true, + // filesystem: {}, + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // replacement: + // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", + // }, + // ], + // }, + // ], + // }, + // { + // step: { + // name: "SolveTracebackStep", + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // description: "Running step: SolveTracebackStep", + // }, + // output: [null, null], + // }, + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [null, null], + // }, + // ], + // current_index: 0, + // } as any + // ); + + const { send: websocketSend } = useContinueWebsocket(serverUrl, (msg) => { + let data = JSON.parse(msg.data); + if (data.messageType === "state") { + setWaitingForSteps(data.state.active); + setHistory(data.state.history); + setUserInputQueue(data.state.user_input_queue); + } + }); + + // useEffect(() => { + // (async () => { + // if (sessionId && props.firstObservation) { + // let resp = await fetch(serverUrl + "/observation", { + // method: "POST", + // headers: new Headers({ + // "x-continue-session-id": sessionId, + // }), + // body: JSON.stringify({ + // observation: props.firstObservation, + // }), + // }); + // } + // })(); + // }, [props.firstObservation]); + + const mainTextInputRef = useRef(null); + + useEffect(() => { + if (mainTextInputRef.current) { + mainTextInputRef.current.focus(); + let handler = (event: any) => { + if (event.data.type === "focusContinueInput") { + mainTextInputRef.current?.focus(); + } + }; + window.addEventListener("message", handler); + return () => { + window.removeEventListener("message", handler); + }; + } + }, [mainTextInputRef]); + + const onMainTextInput = () => { + if (mainTextInputRef.current) { + let value = mainTextInputRef.current.value; + setWaitingForSteps(true); + websocketSend({ + messageType: "main_input", + value: value, + }); + setUserInputQueue((queue) => { + return [...queue, value]; + }); + mainTextInputRef.current.value = ""; + mainTextInputRef.current.style.height = ""; + } + }; + + const onStepUserInput = (input: string, index: number) => { + console.log("Sending step user input", input, index); + websocketSend({ + messageType: "step_user_input", + value: input, + index, + }); + }; + + // const iterations = useSelector(selectIterations); + return ( + + {history?.timeline.map((node: HistoryNode, index: number) => { + return ( + { + onStepUserInput(input, index); + }} + inFuture={index > history?.current_index} + historyNode={node} + onRefinement={(input: string) => { + websocketSend({ + messageType: "refinement_input", + value: input, + index, + }); + }} + onReverse={() => { + websocketSend({ + messageType: "reverse", + index, + }); + }} + /> + ); + })} + {waitingForSteps && } + +
+ {userInputQueue.map((input) => { + return {input}; + })} +
+ + { + if (e.key === "Enter") { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); + } + }} + rows={1} + onChange={() => { + let textarea = mainTextInputRef.current!; + textarea.style.height = ""; /* Reset the height*/ + textarea.style.height = + Math.min(textarea.scrollHeight - 15, 500) + "px"; + }} + > + +
+ ); +} + +export default Notebook; diff --git a/extension/react-app/src/tabs/welcome.tsx b/extension/react-app/src/tabs/welcome.tsx new file mode 100644 index 00000000..c29d260a --- /dev/null +++ b/extension/react-app/src/tabs/welcome.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function WelcomeTab() { + return ( +
+

Welcome to Continue

+ +

+ Learn more in the{" "} + + Continue User Guide + {" "} +

+

Send Nate or Ty your feedback:

+

1. What excites you about Continue?

+

2. What did you struggle with when using Continue?

+

3. How do you wish Continue worked?

+
+ ); +} + +export default WelcomeTab; diff --git a/extension/react-app/src/util/api.ts b/extension/react-app/src/util/api.ts new file mode 100644 index 00000000..bdec1d20 --- /dev/null +++ b/extension/react-app/src/util/api.ts @@ -0,0 +1,43 @@ +import { + Configuration, + DebugApi, + UnittestApi, + ChatApi, +} from "../../../src/client"; +import { useSelector } from "react-redux"; +import { useEffect, useState } from "react"; +import { RootStore } from "../redux/store"; + +export function useApi() { + const apiUrl = useSelector((state: RootStore) => state.config.apiUrl); + const vscMachineId = useSelector( + (state: RootStore) => state.config.vscMachineId + ); + const [debugApi, setDebugApi] = useState(); + const [unittestApi, setUnittestApi] = useState(); + const [chatApi, setChatApi] = useState(); + + useEffect(() => { + if (apiUrl && vscMachineId) { + let config = new Configuration({ + basePath: apiUrl, + fetchApi: fetch, + middleware: [ + { + pre: async (context) => { + context.init.headers = { + ...context.init.headers, + "x-vsc-machine-id": vscMachineId, + }; + }, + }, + ], + }); + setDebugApi(new DebugApi(config)); + setUnittestApi(new UnittestApi(config)); + setChatApi(new ChatApi(config)); + } + }, [apiUrl, vscMachineId]); + + return { debugApi, unittestApi, chatApi }; +} diff --git a/extension/react-app/src/util/editCache.ts b/extension/react-app/src/util/editCache.ts new file mode 100644 index 00000000..b8071127 --- /dev/null +++ b/extension/react-app/src/util/editCache.ts @@ -0,0 +1,89 @@ +import { useApi } from "../util/api"; +import { FileEdit, SerializedDebugContext } from "../../../src/client"; +import { useCallback, useEffect, useState } from "react"; + +export function useEditCache() { + const { debugApi } = useApi(); + + const fetchNewEdit = useCallback( + async (debugContext: SerializedDebugContext) => { + return ( + await debugApi?.editEndpointDebugEditPost({ + serializedDebugContext: debugContext, + }) + )?.completion; + }, + [debugApi] + ); + + const [editCache, setEditCache] = useState(new EditCache(fetchNewEdit)); + + useEffect(() => { + setEditCache(new EditCache(fetchNewEdit)); + }, [fetchNewEdit]); + + return editCache; +} + +/** + * Stores preloaded edits, invalidating based off of debug context changes + */ +class EditCache { + private _lastDebugContext: SerializedDebugContext | undefined; + private _cachedEdits: FileEdit[] | undefined; + private _fetchNewEdit: ( + debugContext: SerializedDebugContext + ) => Promise; + private _debounceTimer: NodeJS.Timeout | undefined; + + private _debugContextChanged(debugContext: SerializedDebugContext): boolean { + if (!this._lastDebugContext) { + return true; + } + + return ( + JSON.stringify(this._lastDebugContext) !== JSON.stringify(debugContext) + ); + } + + private _debugContextComplete(debugContext: SerializedDebugContext): boolean { + return debugContext.rangesInFiles.length > 0; + } + + public async preloadEdit(debugContext: SerializedDebugContext) { + if (this._debounceTimer) { + clearTimeout(this._debounceTimer); + } + if ( + this._debugContextComplete(debugContext) && + this._debugContextChanged(debugContext) + ) { + this._debounceTimer = setTimeout(async () => { + console.log("Preloading edits"); + this._cachedEdits = await this._fetchNewEdit(debugContext); + this._lastDebugContext = debugContext; + }, 200); + } + } + + public async getEdit( + debugContext: SerializedDebugContext + ): Promise { + if (this._debugContextChanged(debugContext)) { + console.log("Cache miss"); + this._cachedEdits = await this._fetchNewEdit(debugContext); + } else { + console.log("Cache hit"); + } + + return this._cachedEdits!; + } + + constructor( + fetchNewEdit: ( + debugContext: SerializedDebugContext + ) => Promise + ) { + this._fetchNewEdit = fetchNewEdit; + } +} diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts new file mode 100644 index 00000000..458f9d95 --- /dev/null +++ b/extension/react-app/src/util/index.ts @@ -0,0 +1,27 @@ +import { RangeInFile } from "../../../src/client"; + +export function readRangeInVirtualFileSystem( + rangeInFile: RangeInFile, + filesystem: { [filepath: string]: string } +): string | undefined { + const range = rangeInFile.range; + + let data = filesystem[rangeInFile.filepath]; + if (data === undefined) { + console.log("File not found"); + return undefined; + } else { + let lines = data.toString().split("\n"); + if (range.start.line === range.end.line) { + return lines[rangeInFile.range.start.line].slice( + rangeInFile.range.start.character, + rangeInFile.range.end.character + ); + } else { + let firstLine = lines[range.start.line].slice(range.start.character); + let lastLine = lines[range.end.line].slice(0, range.end.character); + let middleLines = lines.slice(range.start.line + 1, range.end.line); + return [firstLine, ...middleLines, lastLine].join("\n"); + } + } +} diff --git a/extension/react-app/src/vite-env.d.ts b/extension/react-app/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/extension/react-app/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/extension/react-app/src/vscode/index.ts b/extension/react-app/src/vscode/index.ts new file mode 100644 index 00000000..7e373cd9 --- /dev/null +++ b/extension/react-app/src/vscode/index.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from "react"; +import "vscode-webview"; + +declare const vscode: any; + +export function postVscMessage(type: string, data: any) { + if (typeof vscode === "undefined") { + return; + } + vscode.postMessage({ + type, + ...data, + }); +} + +export async function vscRequest(type: string, data: any): Promise { + return new Promise((resolve) => { + const handler = (event: any) => { + if (event.data.type === type) { + window.removeEventListener("message", handler); + resolve(event.data); + } + }; + window.addEventListener("message", handler); + postVscMessage(type, data); + }); +} + +export function useVscMessageValue( + messageType: string | string[], + initialValue?: any +) { + const [value, setValue] = useState(initialValue); + window.addEventListener("message", (event) => { + if (event.data.type === messageType) { + setValue(event.data.value); + } + }); + return [value, setValue]; +} + +export async function withProgress(title: string, fn: () => Promise) { + postVscMessage("withProgress", { title, done: false }); + return fn().finally(() => { + postVscMessage("withProgress", { title, done: true }); + }); +} diff --git a/extension/react-app/tailwind.config.cjs b/extension/react-app/tailwind.config.cjs new file mode 100644 index 00000000..276d3b8a --- /dev/null +++ b/extension/react-app/tailwind.config.cjs @@ -0,0 +1,22 @@ +/** @type {import('tailwindcss').Config} */ +const colors = require("tailwindcss/colors"); + +module.exports = { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + "./src/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + colors: { + "vsc-background": "rgb(var(--vsc-background) / )", + "secondary-dark": "rgb(var(--secondary-dark) / )", + ...colors, + }, + }, + plugins: [], + corePlugins: { + preflight: false, + }, +}; diff --git a/extension/react-app/tsconfig.json b/extension/react-app/tsconfig.json new file mode 100644 index 00000000..3d0a51a8 --- /dev/null +++ b/extension/react-app/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/extension/react-app/tsconfig.node.json b/extension/react-app/tsconfig.node.json new file mode 100644 index 00000000..9d31e2ae --- /dev/null +++ b/extension/react-app/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/extension/react-app/vite.config.ts b/extension/react-app/vite.config.ts new file mode 100644 index 00000000..25c62f78 --- /dev/null +++ b/extension/react-app/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import tailwindcss from "tailwindcss"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + build: { + // Change the output .js filename to not include a hash + rollupOptions: { + output: { + entryFileNames: `assets/[name].js`, + chunkFileNames: `assets/[name].js`, + assetFileNames: `assets/[name].[ext]`, + }, + }, + }, +}); -- cgit v1.2.3-70-g09d2 From d3db75ee7e2483f86ba5eb8856dd59aaadfae285 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 23:33:47 -0400 Subject: posthog setup in react app --- extension/react-app/package-lock.json | 39 ++++++++++++++++++++++++++++ extension/react-app/package.json | 1 + extension/react-app/src/main.tsx | 25 ++++++++++++------ extension/src/activation/environmentSetup.ts | 4 +-- 4 files changed, 59 insertions(+), 10 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index 1ba8cfe8..dbcbc5cc 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", @@ -1506,6 +1507,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2528,6 +2534,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/posthog-js": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz", + "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==", + "dependencies": { + "fflate": "^0.4.1", + "rrweb-snapshot": "^1.1.14" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -2778,6 +2793,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-snapshot": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz", + "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4196,6 +4216,11 @@ "reusify": "^1.0.4" } }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4803,6 +4828,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "posthog-js": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz", + "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==", + "requires": { + "fflate": "^0.4.1", + "rrweb-snapshot": "^1.1.14" + } + }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4964,6 +4998,11 @@ "fsevents": "~2.3.2" } }, + "rrweb-snapshot": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz", + "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ==" + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 3993b030..7d1211de 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -11,6 +11,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx index 791f139e..1b94dc82 100644 --- a/extension/react-app/src/main.tsx +++ b/extension/react-app/src/main.tsx @@ -1,10 +1,19 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; + +posthog.init("phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs", { + api_host: "https://app.posthog.com", +}); + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - , -) + + + + +); diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 4816b4b1..1e921c18 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -59,7 +59,7 @@ async function setupPythonEnv() { } let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; - let activateCmd = "source env/bin/activate"; + let activateCmd = ". env/bin/activate"; let pipUpgradeCmd = `${pipCmd} install --upgrade pip`; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; @@ -147,7 +147,7 @@ export async function startContinuePythonServer() { console.log("Error checking for existing server", e); } - let activateCmd = "source env/bin/activate"; + let activateCmd = ". env/bin/activate"; let pythonCmd = "python3"; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; -- cgit v1.2.3-70-g09d2 From 056497801039c1b8f5d4600531f5c90dbd375a38 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 00:40:10 -0400 Subject: READMEs --- README.md | 7 +------ continuedev/README.md | 8 ++++++++ extension/DEV_README.md | 7 +++++++ extension/examples/README.md | 3 +++ extension/react-app/README.md | 3 +++ extension/schema/README.md | 3 +++ 6 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 extension/DEV_README.md create mode 100644 extension/examples/README.md create mode 100644 extension/react-app/README.md create mode 100644 extension/schema/README.md (limited to 'extension/react-app') diff --git a/README.md b/README.md index 18525fcd..dc1ad7de 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # Continue -Try out latest version: - -- `cd extension` -- `npm run package` -- `cd build` -- `code --install-extension continue-0.0.1.vsix` +[Download for VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue) Resources diff --git a/continuedev/README.md b/continuedev/README.md index 5af08e24..5658e8de 100644 --- a/continuedev/README.md +++ b/continuedev/README.md @@ -1,3 +1,7 @@ +# Continue + +Continue is a Python library for automating repetitive sequences of software development tasks using language models. Using our VS Code extension, you can build, run, and refine these recipes as they natively interact with your codebase. Download on [our GitHub](https://github.com/continuedev/continue). + ## Steps to start - `cd continue/continue` @@ -17,3 +21,7 @@ Same steps, then `uvicorn continue.src.server.main:app --reload`. ## To build Run `poetry build` and it will output wheel and tarball files in `./dist`. + +## Writing Steps + +See the `src/continuedev/libs/steps` folder for examples of writing a Continue step. diff --git a/extension/DEV_README.md b/extension/DEV_README.md new file mode 100644 index 00000000..7049da45 --- /dev/null +++ b/extension/DEV_README.md @@ -0,0 +1,7 @@ +# Continue VS Code Extension + +This is the Continue VS Code Extension. Its primary jobs are + +1. Implement the IDE side of the Continue IDE protocol, allowing a Continue server to interact natively in an IDE. This happens in `src/continueIdeClient.ts`. +2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openDebugPanel` command, as defined in `src/commands.ts`. +3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `scripts/` (unless extension settings define a server URL other than localhost:8000, in which case the extension will just connect to that). diff --git a/extension/examples/README.md b/extension/examples/README.md new file mode 100644 index 00000000..c95b9220 --- /dev/null +++ b/extension/examples/README.md @@ -0,0 +1,3 @@ +# Examples + +This folder contains example code used in Continue demos. diff --git a/extension/react-app/README.md b/extension/react-app/README.md new file mode 100644 index 00000000..006b6b11 --- /dev/null +++ b/extension/react-app/README.md @@ -0,0 +1,3 @@ +# Continue React App + +The Continue React app is a notebook-like interface to the Continue server. It allows the user to submit arbitrary text input, then communicates with the server to takes steps, which are displayed as a sequence of editable cells. The React app should sit beside an IDE, as in the VS Code extension. diff --git a/extension/schema/README.md b/extension/schema/README.md new file mode 100644 index 00000000..9c97c0eb --- /dev/null +++ b/extension/schema/README.md @@ -0,0 +1,3 @@ +# Schema + +These are files autogenerated by `npm run typegen`. They come originally from the `continuedev` Python package's Pydantic types. -- cgit v1.2.3-70-g09d2 From e02da4c8fada20b7e6bdd80d257a868bbd6b0d0f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 19:16:10 -0400 Subject: websockets through vscode messaging for codespaces --- extension/react-app/src/hooks/useWebsocket.ts | 133 ++++++++++++++++++++++---- extension/src/debugPanel.ts | 95 ++++++++++++++++++ 2 files changed, 207 insertions(+), 21 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 147172bd..6e8e68fa 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,13 +1,100 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; +import { postVscMessage } from "../vscode"; + +abstract class Messenger { + abstract send(data: string): void; +} + +class VscodeMessenger extends Messenger { + url: string; + + constructor( + url: string, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ) { + super(); + this.url = url; + window.addEventListener("message", (event: any) => { + switch (event.data.type) { + case "websocketForwardingMessage": + onMessage(event.data); + break; + case "websocketForwardingOpen": + onOpen(this); + break; + case "websocketForwardingClose": + onClose(this); + break; + } + }); + + postVscMessage("websocketForwardingOpen", { url: this.url }); + } + + send(data: string) { + postVscMessage("websocketForwardingMessage", { + message: data, + url: this.url, + }); + } +} + +class WebsocketMessenger extends Messenger { + websocket: WebSocket; + constructor( + websocket: WebSocket, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ) { + super(); + this.websocket = websocket; + + websocket.addEventListener("close", () => { + onClose(this); + }); + + websocket.addEventListener("open", () => { + onOpen(this); + }); + + websocket.addEventListener("message", (event) => { + onMessage(event.data); + }); + } + + static async connect( + url: string, + sessionId: string, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ): Promise { + const ws = new WebSocket(url); + + return new Promise((resolve, reject) => { + ws.addEventListener("open", () => { + resolve(new WebsocketMessenger(ws, onMessage, onOpen, onClose)); + }); + }); + } + + send(data: string) { + this.websocket.send(JSON.stringify(data)); + } +} function useContinueWebsocket( serverUrl: string, - onMessage: (message: { data: any }) => void + onMessage: (message: { data: any }) => void, + useVscodeMessagePassing: boolean = true ) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); - const [websocket, setWebsocket] = useState(undefined); + const [websocket, setWebsocket] = useState(undefined); async function connect() { while (!sessionId) { @@ -15,32 +102,36 @@ function useContinueWebsocket( } console.log("Creating websocket", sessionId); + console.log("Using vscode message passing", useVscodeMessagePassing); - const wsUrl = - serverUrl.replace("http", "ws") + - "/notebook/ws?session_id=" + - encodeURIComponent(sessionId); - - const ws = new WebSocket(wsUrl); - setWebsocket(ws); + const onClose = (messenger: Messenger) => { + console.log("Websocket closed"); + setWebsocket(undefined); + }; - // Set up callbacks - ws.onopen = () => { + const onOpen = (messenger: Messenger) => { console.log("Websocket opened"); - ws.send(JSON.stringify({ sessionId })); + messenger.send(JSON.stringify({ sessionId })); }; - ws.onmessage = (msg) => { - onMessage(msg); - console.log("Got message", msg); - }; + const url = + serverUrl.replace("http", "ws") + + "/notebook/ws?session_id=" + + encodeURIComponent(sessionId); - ws.onclose = (msg) => { - console.log("Websocket closed"); - setWebsocket(undefined); - }; + const messenger: Messenger = useVscodeMessagePassing + ? new VscodeMessenger(url, onMessage, onOpen, onClose) + : await WebsocketMessenger.connect( + url, + sessionId, + onMessage, + onOpen, + onClose + ); + + setWebsocket(messenger); - return ws; + return messenger; } async function getConnection() { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 66829836..4192595c 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,6 +16,7 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; +const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -87,6 +88,46 @@ class StreamManager { } } +let websocketConnections: { [url: string]: WebsocketConnection | undefined } = + {}; + +class WebsocketConnection { + private _ws: WebSocket; + private _onMessage: (message: string) => void; + private _onOpen: () => void; + private _onClose: () => void; + + constructor( + url: string, + onMessage: (message: string) => void, + onOpen: () => void, + onClose: () => void + ) { + this._ws = new WebSocket(url); + this._onMessage = onMessage; + this._onOpen = onOpen; + this._onClose = onClose; + + this._ws.onmessage = (event) => { + this._onMessage(event.data); + }; + this._ws.onclose = () => { + this._onClose(); + }; + this._ws.onopen = () => { + this._onOpen(); + }; + } + + public send(message: string) { + this._ws.send(message); + } + + public close() { + this._ws.close(); + } +} + let streamManager = new StreamManager(); export let debugPanelWebview: vscode.Webview | undefined; @@ -147,6 +188,39 @@ export function setupDebugPanel( }); }); + async function connectWebsocket(url: string) { + return new Promise((resolve, reject) => { + const onMessage = (message: any) => { + panel.webview.postMessage({ + type: "websocketForwardingMessage", + url, + data: message, + }); + }; + const onOpen = () => { + panel.webview.postMessage({ + type: "websocketForwardingOpen", + url, + }); + resolve(null); + }; + const onClose = () => { + websocketConnections[url] = undefined; + panel.webview.postMessage({ + type: "websocketForwardingClose", + url, + }); + }; + const connection = new WebsocketConnection( + url, + onMessage, + onOpen, + onClose + ); + websocketConnections[url] = connection; + }); + } + panel.webview.onDidReceiveMessage(async (data) => { switch (data.type) { case "onLoad": { @@ -158,6 +232,27 @@ export function setupDebugPanel( }); break; } + + case "websocketForwardingOpen": { + let url = data.url; + if (typeof websocketConnections[url] === "undefined") { + await connectWebsocket(url); + } + break; + } + case "websocketForwardingMessage": { + let url = data.url; + let connection = websocketConnections[url]; + if (typeof connection === "undefined") { + await connectWebsocket(url); + } + connection = websocketConnections[url]; + if (typeof connection === "undefined") { + throw new Error("Failed to connect websocket in VS Code Extension"); + } + connection.send(data.message); + break; + } case "listTenThings": { sendTelemetryEvent(TelemetryEvent.GenerateIdeas); let resp = await debugApi.listtenDebugListPost({ -- cgit v1.2.3-70-g09d2 From 25770db64e613140f9c060f28f7e79e3ca593519 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 22:18:18 -0400 Subject: UI Cleanup --- extension/react-app/src/components/CodeBlock.tsx | 3 ++- extension/react-app/src/components/DebugPanel.tsx | 4 +++- extension/react-app/src/index.css | 2 +- extension/react-app/src/tabs/chat/MessageDiv.tsx | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx index 4c10ab23..e0336554 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -6,7 +6,8 @@ import { defaultBorderRadius, vscBackground } from "."; import { Clipboard } from "@styled-icons/heroicons-outline"; const StyledPre = styled.pre` - overflow: scroll; + overflow-y: scroll; + word-wrap: normal; border: 1px solid gray; border-radius: ${defaultBorderRadius}; background-color: ${vscBackground}; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index ed00571b..9dacc624 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -36,7 +36,8 @@ const GradientContainer = styled.div` const MainDiv = styled.div` height: 100%; border-radius: ${defaultBorderRadius}; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; scrollbar-base-color: transparent; /* background: ${vscBackground}; */ background-color: #1e1e1ede; @@ -107,6 +108,7 @@ function DebugPanel(props: DebugPanelProps) { className={ tab.title === "Chat" ? "overflow-hidden" : "overflow-scroll" } + style={{ scrollbarGutter: "stable both-edges" }} > {tab.element} diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index dd38eec3..20599d30 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -21,7 +21,7 @@ html, body, #root { - height: calc(100% - 7px); + height: calc(100%); } body { diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx index ab632220..ad81f5e9 100644 --- a/extension/react-app/src/tabs/chat/MessageDiv.tsx +++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx @@ -20,7 +20,8 @@ const Container = styled.div` margin: 3px; width: fit-content; max-width: 75%; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; word-wrap: break-word; -ms-word-wrap: break-word; height: fit-content; -- cgit v1.2.3-70-g09d2 From 22245d2cbf90daa9033d8551207aa986069d8b24 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 18:31:25 -0400 Subject: (much!) faster inference with starcoder --- continuedev/src/continuedev/core/agent.py | 3 +- continuedev/src/continuedev/core/env.py | 19 +++++++- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/core/sdk.py | 25 ++++++++++ .../src/continuedev/libs/llm/hf_inference_api.py | 25 ++++++++++ continuedev/src/continuedev/libs/steps/main.py | 57 ++++++++++++++++++++-- continuedev/src/continuedev/server/notebook.py | 4 +- .../react-app/src/components/StepContainer.tsx | 21 ++------ 8 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 continuedev/src/continuedev/libs/llm/hf_inference_api.py (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 6d1f542e..329e3d4c 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -49,9 +49,10 @@ class Agent(ContinueBaseModel): async def wait_for_user_input(self) -> str: self._active = False self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) + user_input = await self._user_input_queue.get(self.history.current_index) self._active = True self.update_subscribers() + return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index d7275b41..6267ed60 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -1,7 +1,22 @@ from dotenv import load_dotenv import os -load_dotenv() +def get_env_var(var_name: str): + load_dotenv() + return os.getenv(var_name) -openai_api_key = os.getenv("OPENAI_API_KEY") + +def save_env_var(var_name: str, var_value: str): + with open('.env', 'r') as f: + lines = f.readlines() + with open('.env', 'w') as f: + values = {} + for line in lines: + key, value = line.split('=') + value = value.replace('"', '') + values[key] = value + + values[var_name] = var_value + for key, value in values.items(): + f.write(f'{key}="{value}"\n') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 504d5ff1..07101576 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return FasterEditHighlightedCodeStep(user_input=observation.user_input) + return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 3559e9d7..750b335d 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -3,10 +3,12 @@ from typing import Coroutine, Union from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM +from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * +from .env import get_env_var, save_env_var class Agent: @@ -18,11 +20,22 @@ class ContinueSDKSteps: self.sdk = sdk +class Models: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + async def starcoder(self): + api_key = await self.sdk.get_user_secret( + 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + return HuggingFaceInferenceAPI(api_key=api_key) + + class ContinueSDK: """The SDK provided as parameters to a step""" llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps + models: Models __agent: Agent def __init__(self, agent: Agent, llm: Union[LLM, None] = None): @@ -33,6 +46,7 @@ class ContinueSDK: self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) + self.models = Models(self) @property def history(self) -> History: @@ -80,3 +94,14 @@ class ContinueSDK: async def delete_directory(self, path: str): return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) + + async def get_user_secret(self, env_var: str, prompt: str) -> str: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text + save_env_var(env_var, val) + return val diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py new file mode 100644 index 00000000..83852d27 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -0,0 +1,25 @@ +from ..llm import LLM +import requests + +DEFAULT_MAX_TOKENS = 2048 +DEFAULT_MAX_TIME = 120. + + +class HuggingFaceInferenceAPI(LLM): + api_key: str + model: str = "bigcode/starcoder" + + def complete(self, prompt: str, **kwargs): + """Return the completion of the text with the given temperature.""" + API_URL = f"https://api-inference.huggingface.co/models/{self.model}" + headers = { + "Authorization": f"Bearer {self.api_key}"} + + response = requests.post(API_URL, headers=headers, json={ + "inputs": prompt, "parameters": { + "max_new_tokens": DEFAULT_MAX_TOKENS, + "max_time": DEFAULT_MAX_TIME, + "return_full_text": False, + } + }) + return response.json()[0]["generated_text"] diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 4f4f80e3..c8a85800 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,4 +1,6 @@ -from typing import Callable, Coroutine, List, Union +from typing import Coroutine, List, Union + +from pydantic import BaseModel from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM @@ -8,9 +10,10 @@ from ...models.filesystem import RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ...core.main import History, Policy, Step, ContinueSDK, Observation +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...core.observation import Observation import subprocess -import json from .core.core import EditCodeStep @@ -36,6 +39,10 @@ class RunCodeStep(Step): return None +class Policy(BaseModel): + pass + + class RunPolicyUntilDoneStep(Step): policy: "Policy" @@ -206,6 +213,50 @@ class FasterEditHighlightedCodeStep(Step): return None +class StarCoderEditHighlightedCodeStep(Step): + user_input: str + hide = True + _prompt: str = "{code}{user_request}" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing highlighted code" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + range_in_files = await sdk.ide.getHighlightedCode() + if len(range_in_files) == 0: + # Get the full contents of all open files + files = await sdk.ide.getOpenFiles() + contents = {} + for file in files: + contents[file] = await sdk.ide.readFile(file) + + range_in_files = [RangeInFile.from_entire_file( + filepath, content) for filepath, content in contents.items()] + + rif_with_contents = [] + for range_in_file in range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents + + for rif in rif_with_contents: + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input) + completion = str((await sdk.models.starcoder()).complete(prompt)) + eot_token = "<|endoftext|>" + if completion.endswith(eot_token): + completion = completion[:completion.rindex(eot_token)] + + await sdk.ide.applyFileSystemEdit( + FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) + await sdk.ide.saveFile(rif.filepath) + await sdk.ide.setFileOpen(rif.filepath) + + class EditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index bfd7a09c..c5dcea31 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -12,7 +12,7 @@ from ..libs.steps.nate import ImplementAbstractMethodStep from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import openai_api_key +from ..core.env import get_env_var import asyncio import nest_asyncio nest_asyncio.apply() @@ -75,7 +75,7 @@ class SessionManager: def new_session(self, ide: AbstractIdeProtocolServer) -> str: cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), + agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), policy=DemoPolicy(cmd=cmd), ide=ide) session_id = str(uuid4()) session = Session(session_id=session_id, agent=agent) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 03649b66..36b3d99a 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -144,6 +144,9 @@ function StepContainer(props: StepContainerProps) { onSubmit={(ev) => { props.onUserInput(ev.currentTarget.value); }} + onClick={(e) => { + e.stopPropagation(); + }} /> )} {props.historyNode.step.name === "Waiting for user confirmation" && ( @@ -165,24 +168,6 @@ function StepContainer(props: StepContainerProps) { /> )} - - {open && ( - <> - {/* {props.historyNode.observation && ( - - Error Here - - )} */} - {/* {props.iterationContext.suggestedChanges.map((sc) => { - return ( - - {sc.filepath} - {sc.replacement} - - ); - })} */} - - )} -- cgit v1.2.3-70-g09d2 From dd5b9f6b7f08f178d6034a57f97faea38442eb0a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 16:13:01 -0400 Subject: checkpoint! protocol reform and it works now --- continuedev/src/continuedev/core/agent.py | 34 ++-- continuedev/src/continuedev/core/env.py | 4 + continuedev/src/continuedev/core/main.py | 14 +- continuedev/src/continuedev/core/policy.py | 17 +- continuedev/src/continuedev/core/sdk.py | 13 +- continuedev/src/continuedev/libs/steps/chroma.py | 2 +- .../src/continuedev/libs/steps/core/core.py | 33 ++-- .../src/continuedev/libs/steps/draft/dlt.py | 2 +- continuedev/src/continuedev/libs/steps/main.py | 20 +- .../src/continuedev/libs/steps/migration.py | 2 +- continuedev/src/continuedev/libs/steps/nate.py | 6 +- continuedev/src/continuedev/libs/steps/pytest.py | 2 +- continuedev/src/continuedev/libs/steps/ty.py | 2 +- continuedev/src/continuedev/server/ide.py | 50 ++--- continuedev/src/continuedev/server/main.py | 2 +- continuedev/src/continuedev/server/notebook.py | 207 +++++++-------------- .../src/continuedev/server/notebook_protocol.py | 28 +++ .../src/continuedev/server/session_manager.py | 101 ++++++++++ extension/package-lock.json | 19 ++ extension/package.json | 7 +- .../src/hooks/ContinueNotebookClientProtocol.ts | 13 ++ extension/react-app/src/hooks/messenger.ts | 108 +++++++++++ .../src/hooks/useContinueNotebookProtocol.ts | 49 +++++ extension/react-app/src/hooks/useWebsocket.ts | 171 +++-------------- extension/react-app/src/hooks/vscodeMessenger.ts | 68 +++++++ extension/react-app/src/tabs/notebook.tsx | 70 +++---- extension/react-app/src/vscode/index.ts | 1 + extension/react-app/tsconfig.json | 2 +- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 53104 -> 56070 bytes extension/src/activation/activate.ts | 4 +- extension/src/activation/environmentSetup.ts | 104 +++++++---- extension/src/commands.ts | 4 +- extension/src/continueIdeClient.ts | 146 ++++----------- extension/src/debugPanel.ts | 26 ++- extension/src/extension.ts | 7 +- extension/src/test/runTest.ts | 30 +-- extension/src/util/messenger.ts | 108 +++++++++++ 37 files changed, 864 insertions(+), 612 deletions(-) create mode 100644 continuedev/src/continuedev/server/notebook_protocol.py create mode 100644 continuedev/src/continuedev/server/session_manager.py create mode 100644 extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts create mode 100644 extension/react-app/src/hooks/messenger.ts create mode 100644 extension/react-app/src/hooks/useContinueNotebookProtocol.ts create mode 100644 extension/react-app/src/hooks/vscodeMessenger.ts create mode 100644 extension/src/util/messenger.ts (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 329e3d4c..7f7466a2 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -13,7 +13,6 @@ from .sdk import ContinueSDK class Agent(ContinueBaseModel): - llm: LLM policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() @@ -31,27 +30,24 @@ class Agent(ContinueBaseModel): def get_full_state(self) -> FullState: return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - def on_update(self, callback: Callable[["FullState"], None]): + def on_update(self, callback: Coroutine["FullState", None, None]): """Subscribe to changes to state""" self._on_update_callbacks.append(callback) - def update_subscribers(self): + async def update_subscribers(self): full_state = self.get_full_state() for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + await callback(full_state) def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) + self._user_input_queue.post(str(index), input) async def wait_for_user_input(self) -> str: self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) + await self.update_subscribers() + user_input = await self._user_input_queue.get(str(self.history.current_index)) self._active = True - self.update_subscribers() + await self.update_subscribers() return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] @@ -62,9 +58,9 @@ class Agent(ContinueBaseModel): current_step = self.history.get_current().step self.history.step_back() if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) + await current_step.reverse(ContinueSDK(self)) - self.update_subscribers() + await self.update_subscribers() except Exception as e: print(e) @@ -94,17 +90,17 @@ class Agent(ContinueBaseModel): # Run step self._step_depth += 1 - observation = await step(self.__get_step_params(step)) + observation = await step(ContinueSDK(self)) self._step_depth -= 1 # Add observation to history self.history.get_current().observation = observation # Update its description - step._set_description(await step.describe(self.llm)) + step._set_description(await step.describe(ContinueSDK(self))) # Call all subscribed callbacks - self.update_subscribers() + await self.update_subscribers() return observation @@ -138,7 +134,7 @@ class Agent(ContinueBaseModel): # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools for callback in self._on_update_callbacks: - callback(None) + await callback(None) async def run_from_observation(self, observation: Observation): next_step = self.policy.next(self.history) @@ -158,7 +154,7 @@ class Agent(ContinueBaseModel): async def accept_user_input(self, user_input: str): self._main_user_input_queue.append(user_input) - self.update_subscribers() + await self.update_subscribers() if len(self._main_user_input_queue) > 1: return @@ -167,7 +163,7 @@ class Agent(ContinueBaseModel): # Just run the step that takes user input, and # then up to the policy to decide how to deal with it. self._main_user_input_queue.pop(0) - self.update_subscribers() + await self.update_subscribers() await self.run_from_step(UserInputStep(user_input=user_input)) while len(self._main_user_input_queue) > 0: diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index 6267ed60..edd3297c 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -8,6 +8,10 @@ def get_env_var(var_name: str): def save_env_var(var_name: str, var_value: str): + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write(f'{var_name}="{var_value}"\n') + return with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 51fcd299..6be5139b 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -72,7 +72,7 @@ class ContinueSDK: pass -class SequentialStep: +class Models: pass @@ -94,7 +94,7 @@ class Step(ContinueBaseModel): class Config: copy_on_model_validation = False - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return "Running step: " + self.name @@ -135,6 +135,16 @@ class Step(ContinueBaseModel): return SequentialStep(steps=steps) +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + class ValidatorObservation(Observation): passed: bool observation: Observation diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 07101576..c0ba0f4f 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,20 +1,16 @@ from typing import List, Tuple, Type - -from ..models.main import ContinueBaseModel - from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep -from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma +# from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep class DemoPolicy(Policy): ran_code_last: bool = False - cmd: str def next(self, history: History) -> Step: observation = history.last_observation() @@ -26,18 +22,15 @@ class DemoPolicy(Policy): return CreatePipelineStep() elif "/table" in observation.user_input: return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) - elif "/ask" in observation.user_input: - return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - elif "/edit" in observation.user_input: - return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + # elif "/ask" in observation.user_input: + # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + # elif "/edit" in observation.user_input: + # return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() - if state is None or not self.ran_code_last: - self.ran_code_last = True - return RunCodeStep(cmd=self.cmd) if observation is not None and isinstance(observation, TracebackObservation): self.ran_code_last = False diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 750b335d..6ae0be04 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -4,6 +4,7 @@ from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDir from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI +from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step @@ -29,20 +30,20 @@ class Models: 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') return HuggingFaceInferenceAPI(api_key=api_key) + async def gpt35(self): + api_key = await self.sdk.get_user_secret( + 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") + class ContinueSDK: """The SDK provided as parameters to a step""" - llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps models: Models __agent: Agent - def __init__(self, agent: Agent, llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm + def __init__(self, agent: Agent): self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py index f13a2bab..39424c5c 100644 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ b/continuedev/src/continuedev/libs/steps/chroma.py @@ -40,7 +40,7 @@ class AnswerQuestionChroma(Step): Here is the answer:""") - answer = sdk.llm.complete(prompt) + answer = (await sdk.models.gpt35()).complete(prompt) print(answer) self._answer = answer diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 0338d635..14b3cb80 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -4,27 +4,18 @@ from textwrap import dedent from typing import Coroutine, List, Union from ...llm.prompt_utils import MarkdownStyleEncoderDecoder -from ...util.traceback_parsers import parse_python_traceback - from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents -from ...llm import LLM from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ....core.main import Step +from ....core.main import Step, SequentialStep class ContinueSDK: pass -class SequentialStep(Step): - steps: list[Step] - hide: bool = True - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - for step in self.steps: - observation = await sdk.run_step(step) - return observation +class Models: + pass class ReversibleStep(Step): @@ -52,7 +43,7 @@ def ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "\n".join(self.cmds) async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -81,13 +72,13 @@ class EditCodeStep(Step): _prompt: Union[str, None] = None _completion: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._edit_diffs is None: return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) elif len(self._edit_diffs) == 0: return "No edits made" else: - return llm.complete(dedent(f"""{self._prompt}{self._completion} + return (await models.gpt35()).complete(dedent(f"""{self._prompt}{self._completion} Maximally concise summary of changes in bullet points (can use markdown): """)) @@ -102,7 +93,7 @@ class EditCodeStep(Step): code_string = enc_dec.encode() prompt = self.prompt.format(code=code_string) - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -127,7 +118,7 @@ class EditFileStep(Step): prompt: str hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing file: " + self.filepath async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -145,7 +136,7 @@ class ManualEditStep(ReversibleStep): hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Manual edit step" # TODO - only handling FileEdit here, but need all other types of FileSystemEdits # Also requires the merge_file_edit function @@ -181,7 +172,7 @@ class UserInputStep(Step): name: str = "User Input" hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.user_input async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: @@ -194,7 +185,7 @@ class WaitForUserInputStep(Step): _description: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -207,7 +198,7 @@ class WaitForUserConfirmationStep(Step): prompt: str name: str = "Waiting for user confirmation" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 5ba5692a..460aa0cc 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -10,7 +10,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - source_name = sdk.llm.complete( + source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'{source_name}.py' diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index c8a85800..70c0d4b8 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -11,7 +11,7 @@ from ...core.observation import Observation, TextObservation, TracebackObservati from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent from ...core.main import Step -from ...core.sdk import ContinueSDK +from ...core.sdk import ContinueSDK, Models from ...core.observation import Observation import subprocess from .core.core import EditCodeStep @@ -20,7 +20,7 @@ from .core.core import EditCodeStep class RunCodeStep(Step): cmd: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"Ran command: `{self.cmd}`" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -59,7 +59,7 @@ class RunCommandStep(Step): name: str = "Run command" _description: str = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return self.cmd @@ -125,7 +125,7 @@ class FasterEditHighlightedCodeStep(Step): Here is the description of changes to make: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -154,7 +154,7 @@ class FasterEditHighlightedCodeStep(Step): for rif in rif_with_contents: rif_dict[rif.filepath] = rif.contents - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -215,10 +215,10 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - hide = True + hide = False _prompt: str = "{code}{user_request}" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -271,7 +271,7 @@ This is the user request: This is the code after being changed to perfectly satisfy the user request: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -293,7 +293,7 @@ This is the code after being changed to perfectly satisfy the user request: class FindCodeStep(Step): prompt: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Finding code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -307,7 +307,7 @@ class UserInputStep(Step): class SolveTracebackStep(Step): traceback: Traceback - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"```\n{self.traceback.full_traceback}\n```" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py index f044a60f..7b70422d 100644 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ b/continuedev/src/continuedev/libs/steps/migration.py @@ -15,7 +15,7 @@ class MigrationStep(Step): recent_edits = await sdk.ide.get_recent_edits(self.edited_file) recent_edits_string = "\n\n".join( map(lambda x: x.to_string(), recent_edits)) - description = await sdk.llm.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") + description = await (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) migration_file = f"libs/alembic/versions/{?}.py" contents = await sdk.ide.readFile(migration_file) diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py index a0e728e5..2f84e9d7 100644 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ b/continuedev/src/continuedev/libs/steps/nate.py @@ -45,7 +45,7 @@ Here are additional instructions: Here is a complete set of pytest unit tests: """) - # tests = sdk.llm.complete(prompt) + # tests = (await sdk.models.gpt35()).complete(prompt) tests = ''' import pytest @@ -169,9 +169,9 @@ export class Order { tracking_number: string; }''' time.sleep(2) - # orm_entity = sdk.llm.complete( + # orm_entity = (await sdk.models.gpt35()).complete( # f"{self.sql_str}\n\nWrite a TypeORM entity called {entity_name} for this table, importing as necessary:") - # sdk.llm.complete("What is the name of the entity?") + # (await sdk.models.gpt35()).complete("What is the name of the entity?") await sdk.apply_filesystem_edit(AddFile(filepath=f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", content=orm_entity)) await sdk.ide.setFileOpen(f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", True) diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py index b4e6dfd2..2e83ae2d 100644 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ b/continuedev/src/continuedev/libs/steps/pytest.py @@ -33,5 +33,5 @@ class WritePytestsStep(Step): Here is a complete set of pytest unit tests: """) - tests = sdk.llm.complete(prompt) + tests = (await sdk.models.gpt35()).complete(prompt) await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py index 5ff03f04..9dde7c86 100644 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ b/continuedev/src/continuedev/libs/steps/ty.py @@ -18,7 +18,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - # source_name = sdk.llm.complete( + # source_name = (await sdk.models.gpt35()).complete( # f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'/Users/natesesti/Desktop/continue/extension/examples/python/{source_name}.py' diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index dd1dc463..50296841 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -1,5 +1,6 @@ # This is a separate server from server/main.py import asyncio +import json import os from typing import Any, Dict, List, Type, TypeVar, Union import uuid @@ -90,31 +91,33 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def __init__(self, session_manager: SessionManager): self.session_manager = session_manager - async def _send_json(self, data: Any): - await self.websocket.send_json(data) + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "messageType": message_type, + "data": data + }) async def _receive_json(self, message_type: str) -> Any: return await self.sub_queue.get(message_type) async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: - await self._send_json(data) + await self._send_json(message_type, data) resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) - async def handle_json(self, data: Any): - t = data["messageType"] - if t == "openNotebook": + async def handle_json(self, message_type: str, data: Any): + if message_type == "openNotebook": await self.openNotebook() - elif t == "setFileOpen": + elif message_type == "setFileOpen": await self.setFileOpen(data["filepath"], data["open"]) - elif t == "fileEdits": + elif message_type == "fileEdits": fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif t in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: - self.sub_queue.post(t, data) + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + self.sub_queue.post(message_type, data) else: - raise ValueError("Unknown message type", t) + raise ValueError("Unknown message type", message_type) # ------------------------------- # # Request actions in IDE, doesn't matter which Session @@ -123,24 +126,21 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def setFileOpen(self, filepath: str, open: bool = True): # Agent needs access to this. - await self.websocket.send_json({ - "messageType": "setFileOpen", + await self._send_json("setFileOpen", { "filepath": filepath, "open": open }) async def openNotebook(self): session_id = self.session_manager.new_session(self) - await self._send_json({ - "messageType": "openNotebook", + await self._send_json("openNotebook", { "sessionId": session_id }) async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: ids = [str(uuid.uuid4()) for _ in suggestions] for i in range(len(suggestions)): - self._send_json({ - "messageType": "showSuggestion", + self._send_json("showSuggestion", { "suggestion": suggestions[i], "suggestionId": ids[i] }) @@ -210,8 +210,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def saveFile(self, filepath: str): """Save a file""" - await self._send_json({ - "messageType": "saveFile", + await self._send_json("saveFile", { "filepath": filepath }) @@ -293,10 +292,17 @@ ideProtocolServer = IdeProtocolServer(session_manager) async def websocket_endpoint(websocket: WebSocket): await websocket.accept() print("Accepted websocket connection from, ", websocket.client) - await websocket.send_json({"messageType": "connected"}) + await websocket.send_json({"messageType": "connected", "data": {}}) ideProtocolServer.websocket = websocket while True: - data = await websocket.receive_json() - await ideProtocolServer.handle_json(data) + message = await websocket.receive_text() + message = json.loads(message) + + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + + await ideProtocolServer.handle_json(message_type, data) await websocket.close() diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 11ad1d8f..e87d5fa9 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -32,7 +32,7 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port, log_config="logging.ini") + uvicorn.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c5dcea31..edb61a45 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -1,18 +1,12 @@ -from fastapi import FastAPI, Depends, Header, WebSocket, APIRouter -from typing import Any, Dict, List, Union -from uuid import uuid4 +import json +from fastapi import Depends, Header, WebSocket, APIRouter +from typing import Any, Type, TypeVar, Union from pydantic import BaseModel from uvicorn.main import Server -from ..models.filesystem_edit import FileEditWithFullContents -from ..core.policy import DemoPolicy -from ..core.main import FullState, History, Step -from ..core.agent import Agent -from ..libs.steps.nate import ImplementAbstractMethodStep -from ..core.observation import Observation -from ..libs.llm.openai import OpenAI -from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import get_env_var +from .session_manager import SessionManager, session_manager, Session +from .notebook_protocol import AbstractNotebookProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue import asyncio import nest_asyncio nest_asyncio.apply() @@ -36,160 +30,99 @@ class AppStatus: Server.handle_exit = AppStatus.handle_exit -class Session: - session_id: str - agent: Agent - ws: Union[WebSocket, None] - - def __init__(self, session_id: str, agent: Agent): - self.session_id = session_id - self.agent = agent - self.ws = None - - -class DemoAgent(Agent): - first_seen: bool = False - cumulative_edit_string = "" - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self.cumulative_edit_string += edit.fileEdit.replacement - self._manual_edits_buffer.append(edit) - # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - # FOR DEMO PURPOSES - if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: - self.cumulative_edit_string = "" - asyncio.create_task(self.run_from_step( - ImplementAbstractMethodStep())) - - -class SessionManager: - sessions: Dict[str, Session] = {} - _event_loop: Union[asyncio.BaseEventLoop, None] = None - - def get_session(self, session_id: str) -> Session: - if session_id not in self.sessions: - raise KeyError("Session ID not recognized") - return self.sessions[session_id] - - def new_session(self, ide: AbstractIdeProtocolServer) -> str: - cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), - policy=DemoPolicy(cmd=cmd), ide=ide) - session_id = str(uuid4()) - session = Session(session_id=session_id, agent=agent) - self.sessions[session_id] = session +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) - def on_update(state: FullState): - session_manager.send_ws_data(session_id, { - "messageType": "state", - "state": agent.get_full_state().dict() - }) - agent.on_update(on_update) - asyncio.create_task(agent.run_policy()) - return session_id +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) - def remove_session(self, session_id: str): - del self.sessions[session_id] - def register_websocket(self, session_id: str, ws: WebSocket): - self.sessions[session_id].ws = ws - print("Registered websocket for session", session_id) +T = TypeVar("T", bound=BaseModel) - def send_ws_data(self, session_id: str, data: Any): - if self.sessions[session_id].ws is None: - print(f"Session {session_id} has no websocket") - return +# You should probably abstract away the websocket stuff into a separate class - async def a(): - await self.sessions[session_id].ws.send_json(data) - # Run coroutine in background - if self._event_loop is None or self._event_loop.is_closed(): - self._event_loop = asyncio.new_event_loop() - self._event_loop.run_until_complete(a()) - self._event_loop.close() - else: - self._event_loop.run_until_complete(a()) - self._event_loop.close() +class NotebookProtocolServer(AbstractNotebookProtocolServer): + websocket: WebSocket + session: Session + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + def __init__(self, session: Session): + self.session = session -session_manager = SessionManager() + async def _send_json(self, data: Any): + await self.websocket.send_json(data) + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(message_type) -def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return session_manager.get_session(x_continue_session_id) + async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: + await self._send_json(data) + resp = await self._receive_json(message_type) + return resp_model.parse_obj(resp) + def handle_json(self, message_type: str, data: Any): + try: + if message_type == "main_input": + self.on_main_input(data["input"]) + elif message_type == "step_user_input": + self.on_step_user_input(data["input"], data["index"]) + elif message_type == "refinement_input": + self.on_refinement_input(data["input"], data["index"]) + elif message_type == "reverse_to_index": + self.on_reverse_to_index(data["index"]) + except Exception as e: + print(e) -def websocket_session(session_id: str) -> Session: - return session_manager.get_session(session_id) + async def send_state_update(self): + state = self.session.agent.get_full_state().dict() + await self._send_json({ + "messageType": "state_update", + "state": state + }) + def on_main_input(self, input: str): + # Do something with user input + asyncio.create_task(self.session.agent.accept_user_input(input)) -class StartSessionBody(BaseModel): - config_file_path: Union[str, None] + def on_reverse_to_index(self, index: int): + # Reverse the history to the given index + asyncio.create_task(self.session.agent.reverse_to_index(index)) + def on_step_user_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.give_user_input(input, index)) -class StartSessionResp(BaseModel): - session_id: str + def on_refinement_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.accept_refinement_input(input, index)) @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): await websocket.accept() + print("Session started") session_manager.register_websocket(session.session_id, websocket) - data = await websocket.receive_text() + protocol = NotebookProtocolServer(session) + protocol.websocket = websocket + # Update any history that may have happened before connection - await websocket.send_json({ - "messageType": "state", - "state": session_manager.get_session(session.session_id).agent.get_full_state().dict() - }) - print("Session started", data) + await protocol.send_state_update() + while AppStatus.should_exit is False: - data = await websocket.receive_json() - print("Received data", data) + message = await websocket.receive_json() + print("Received message", message) + if type(message) is str: + message = json.loads(message) - if "messageType" not in data: + if "messageType" not in message or "data" not in message: continue - messageType = data["messageType"] + message_type = message["messageType"] + data = message["data"] - try: - if messageType == "main_input": - # Do something with user input - asyncio.create_task( - session.agent.accept_user_input(data["value"])) - elif messageType == "step_user_input": - asyncio.create_task( - session.agent.give_user_input(data["value"], data["index"])) - elif messageType == "refinement_input": - asyncio.create_task( - session.agent.accept_refinement_input(data["value"], data["index"])) - elif messageType == "reverse": - # Reverse the history to the given index - asyncio.create_task( - session.agent.reverse_to_index(data["index"])) - except Exception as e: - print(e) + protocol.handle_json(message_type, data) print("Closing websocket") await websocket.close() - - -@router.post("/run") -def request_run(step: Step, session=Depends(session)): - """Tell an agent to take a specific action.""" - asyncio.create_task(session.agent.run_from_step(step)) - return "Success" - - -@router.get("/history") -def get_history(session=Depends(session)) -> History: - return session.agent.history - - -@router.post("/observation") -def post_observation(observation: Observation, session=Depends(session)): - asyncio.create_task(session.agent.run_from_observation(observation)) - return "Success" diff --git a/continuedev/src/continuedev/server/notebook_protocol.py b/continuedev/src/continuedev/server/notebook_protocol.py new file mode 100644 index 00000000..c2be82e0 --- /dev/null +++ b/continuedev/src/continuedev/server/notebook_protocol.py @@ -0,0 +1,28 @@ +from typing import Any +from abc import ABC, abstractmethod + + +class AbstractNotebookProtocolServer(ABC): + @abstractmethod + async def handle_json(self, data: Any): + """Handle a json message""" + + @abstractmethod + def on_main_input(self, input: str): + """Called when the user inputs something""" + + @abstractmethod + def on_reverse_to_index(self, index: int): + """Called when the user requests reverse to a previous index""" + + @abstractmethod + def on_refinement_input(self, input: str, index: int): + """Called when the user inputs a refinement""" + + @abstractmethod + def on_step_user_input(self, input: str, index: int): + """Called when the user inputs a step""" + + @abstractmethod + async def send_state_update(self, state: dict): + """Send a state update to the client""" diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py new file mode 100644 index 00000000..b48c21b7 --- /dev/null +++ b/continuedev/src/continuedev/server/session_manager.py @@ -0,0 +1,101 @@ +from fastapi import WebSocket +from typing import Any, Dict, List, Union +from uuid import uuid4 + +from ..models.filesystem_edit import FileEditWithFullContents +from ..core.policy import DemoPolicy +from ..core.main import FullState +from ..core.agent import Agent +from ..libs.steps.nate import ImplementAbstractMethodStep +from .ide_protocol import AbstractIdeProtocolServer +import asyncio +import nest_asyncio +nest_asyncio.apply() + + +class Session: + session_id: str + agent: Agent + ws: Union[WebSocket, None] + + def __init__(self, session_id: str, agent: Agent): + self.session_id = session_id + self.agent = agent + self.ws = None + + +class DemoAgent(Agent): + first_seen: bool = False + cumulative_edit_string = "" + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self.cumulative_edit_string += edit.fileEdit.replacement + self._manual_edits_buffer.append(edit) + # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + # FOR DEMO PURPOSES + if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: + self.cumulative_edit_string = "" + asyncio.create_task(self.run_from_step( + ImplementAbstractMethodStep())) + + +class SessionManager: + sessions: Dict[str, Session] = {} + _event_loop: Union[asyncio.BaseEventLoop, None] = None + + def get_session(self, session_id: str) -> Session: + if session_id not in self.sessions: + raise KeyError("Session ID not recognized") + return self.sessions[session_id] + + def new_session(self, ide: AbstractIdeProtocolServer) -> str: + agent = DemoAgent(policy=DemoPolicy(), ide=ide) + session_id = str(uuid4()) + session = Session(session_id=session_id, agent=agent) + self.sessions[session_id] = session + + async def on_update(state: FullState): + await session_manager.send_ws_data(session_id, "state_update", { + "state": agent.get_full_state().dict() + }) + + agent.on_update(on_update) + asyncio.create_task(agent.run_policy()) + return session_id + + def remove_session(self, session_id: str): + del self.sessions[session_id] + + def register_websocket(self, session_id: str, ws: WebSocket): + self.sessions[session_id].ws = ws + print("Registered websocket for session", session_id) + + async def send_ws_data(self, session_id: str, message_type: str, data: Any): + if self.sessions[session_id].ws is None: + print(f"Session {session_id} has no websocket") + return + + async def a(): + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + + # Run coroutine in background + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + return + if self._event_loop is None or self._event_loop.is_closed(): + self._event_loop = asyncio.new_event_loop() + self._event_loop.run_until_complete(a()) + self._event_loop.close() + else: + self._event_loop.run_until_complete(a()) + self._event_loop.close() + + +session_manager = SessionManager() diff --git a/extension/package-lock.json b/extension/package-lock.json index 20ac24be..04af09d3 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -28,6 +28,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", @@ -2027,6 +2028,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", @@ -9246,6 +9256,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", diff --git a/extension/package.json b/extension/package.json index dc0192c3..c96655a9 100644 --- a/extension/package.json +++ b/extension/package.json @@ -148,7 +148,7 @@ }, "scripts": { "vscode:prepublish": "npm run esbuild-base -- --minify", - "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", + "esbuild-base": "rm -rf ./out && esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", "esbuild": "rm -rf ./out && npm run esbuild-base -- --sourcemap", "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", "test-compile": "tsc -p ./", @@ -160,9 +160,9 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.2.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.5.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, @@ -173,6 +173,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", diff --git a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts new file mode 100644 index 00000000..75fd7373 --- /dev/null +++ b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts @@ -0,0 +1,13 @@ +abstract class AbstractContinueNotebookClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; +} + +export default AbstractContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts new file mode 100644 index 00000000..e2a0bab8 --- /dev/null +++ b/extension/react-app/src/hooks/messenger.ts @@ -0,0 +1,108 @@ +// console.log("Websocket import"); +// const WebSocket = require("ws"); + +export abstract class Messenger { + abstract send(messageType: string, data: object): void; + + abstract onMessageType( + messageType: string, + callback: (data: object) => void + ): void; + + abstract onMessage(callback: (messageType: string, data: any) => void): void; + + abstract onOpen(callback: () => void): void; + + abstract onClose(callback: () => void): void; + + abstract sendAndReceive(messageType: string, data: any): Promise; +} + +export class WebsocketMessenger extends Messenger { + websocket: WebSocket; + private onMessageListeners: { + [messageType: string]: ((data: object) => void)[]; + } = {}; + private onOpenListeners: (() => void)[] = []; + private onCloseListeners: (() => void)[] = []; + private serverUrl: string; + + _newWebsocket(): WebSocket { + // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser. + // if (typeof process === "object") { + // console.log("Using node"); + // // process is only available in Node + // var WebSocket = require("ws"); + // } + + const newWebsocket = new WebSocket(this.serverUrl); + for (const listener of this.onOpenListeners) { + this.onOpen(listener); + } + for (const listener of this.onCloseListeners) { + this.onClose(listener); + } + for (const messageType in this.onMessageListeners) { + for (const listener of this.onMessageListeners[messageType]) { + this.onMessageType(messageType, listener); + } + } + return newWebsocket; + } + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + this.websocket = this._newWebsocket(); + } + + send(messageType: string, data: object) { + const payload = JSON.stringify({ messageType, data }); + if (this.websocket.readyState === this.websocket.OPEN) { + this.websocket.send(payload); + } else { + if (this.websocket.readyState !== this.websocket.CONNECTING) { + this.websocket = this._newWebsocket(); + } + this.websocket.addEventListener("open", () => { + this.websocket.send(payload); + }); + } + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve, reject) => { + const eventListener = (data: any) => { + // THIS ISN"T GETTING CALLED + resolve(data); + this.websocket.removeEventListener("message", eventListener); + }; + this.onMessageType(messageType, eventListener); + this.send(messageType, data); + }); + } + + onMessageType(messageType: string, callback: (data: any) => void): void { + this.websocket.addEventListener("message", (event: any) => { + const msg = JSON.parse(event.data); + if (msg.messageType === messageType) { + callback(msg.data); + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + this.websocket.addEventListener("message", (event) => { + const msg = JSON.parse(event.data); + callback(msg.messageType, msg.data); + }); + } + + onOpen(callback: () => void): void { + this.websocket.addEventListener("open", callback); + } + + onClose(callback: () => void): void { + this.websocket.addEventListener("close", callback); + } +} diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts new file mode 100644 index 00000000..d5ffbf09 --- /dev/null +++ b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts @@ -0,0 +1,49 @@ +import AbstractContinueNotebookClientProtocol from "./ContinueNotebookClientProtocol"; +// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; + +class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; + + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean = false + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + if (useVscodeMessagePassing) { + this.messenger = new VscodeMessenger(serverUrlWithSessionId); + } else { + this.messenger = new WebsocketMessenger(serverUrlWithSessionId); + } + } + + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } + + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } + + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } +} + +export default ContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 6e8e68fa..b98be577 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,158 +1,39 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; +import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; import { postVscMessage } from "../vscode"; -abstract class Messenger { - abstract send(data: string): void; -} - -class VscodeMessenger extends Messenger { - url: string; - - constructor( - url: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.url = url; - window.addEventListener("message", (event: any) => { - switch (event.data.type) { - case "websocketForwardingMessage": - onMessage(event.data); - break; - case "websocketForwardingOpen": - onOpen(this); - break; - case "websocketForwardingClose": - onClose(this); - break; - } - }); - - postVscMessage("websocketForwardingOpen", { url: this.url }); - } - - send(data: string) { - postVscMessage("websocketForwardingMessage", { - message: data, - url: this.url, - }); - } -} - -class WebsocketMessenger extends Messenger { - websocket: WebSocket; - constructor( - websocket: WebSocket, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.websocket = websocket; - - websocket.addEventListener("close", () => { - onClose(this); - }); - - websocket.addEventListener("open", () => { - onOpen(this); - }); - - websocket.addEventListener("message", (event) => { - onMessage(event.data); - }); - } - - static async connect( - url: string, - sessionId: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ): Promise { - const ws = new WebSocket(url); - - return new Promise((resolve, reject) => { - ws.addEventListener("open", () => { - resolve(new WebsocketMessenger(ws, onMessage, onOpen, onClose)); - }); - }); - } - - send(data: string) { - this.websocket.send(JSON.stringify(data)); - } -} - -function useContinueWebsocket( - serverUrl: string, - onMessage: (message: { data: any }) => void, - useVscodeMessagePassing: boolean = true -) { +function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = false) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); - const [websocket, setWebsocket] = useState(undefined); + const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); + const [client, setClient] = useState< + ContinueNotebookClientProtocol | undefined + >(undefined); - async function connect() { - while (!sessionId) { - await new Promise((resolve) => setTimeout(resolve, 300)); + useEffect(() => { + if (!sessionId || !serverHttpUrl) { + if (useVscodeMessagePassing) { + postVscMessage("onLoad", {}); + } + setClient(undefined); + return; } - console.log("Creating websocket", sessionId); - console.log("Using vscode message passing", useVscodeMessagePassing); - - const onClose = (messenger: Messenger) => { - console.log("Websocket closed"); - setWebsocket(undefined); - }; - - const onOpen = (messenger: Messenger) => { - console.log("Websocket opened"); - messenger.send(JSON.stringify({ sessionId })); - }; - - const url = - serverUrl.replace("http", "ws") + + const serverUrlWithSessionId = + serverHttpUrl.replace("http", "ws") + "/notebook/ws?session_id=" + encodeURIComponent(sessionId); - const messenger: Messenger = useVscodeMessagePassing - ? new VscodeMessenger(url, onMessage, onOpen, onClose) - : await WebsocketMessenger.connect( - url, - sessionId, - onMessage, - onOpen, - onClose - ); - - setWebsocket(messenger); - - return messenger; - } - - async function getConnection() { - if (!websocket) { - return await connect(); - } - return websocket; - } - - async function send(message: object) { - let ws = await getConnection(); - ws.send(JSON.stringify(message)); - } - - useEffect(() => { - if (!sessionId) { - return; - } - connect(); - }, [sessionId]); - - return { send }; + console.log("Creating websocket", serverUrlWithSessionId); + console.log("Using vscode message passing", useVscodeMessagePassing); + const newClient = new ContinueNotebookClientProtocol( + serverUrlWithSessionId, + useVscodeMessagePassing + ); + setClient(newClient); + }, [sessionId, serverHttpUrl]); + + return client; } -export default useContinueWebsocket; +export default useContinueNotebookProtocol; diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts new file mode 100644 index 00000000..746c4302 --- /dev/null +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -0,0 +1,68 @@ +import { postVscMessage } from "../vscode"; +// import { Messenger } from "../../../src/util/messenger"; +import { Messenger } from "./messenger"; + +export class VscodeMessenger extends Messenger { + serverUrl: string; + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + postVscMessage("websocketForwardingOpen", { url: this.serverUrl }); + } + + send(messageType: string, data: object) { + postVscMessage("websocketForwardingMessage", { + message: { messageType, data }, + url: this.serverUrl, + }); + } + + onMessageType(messageType: string, callback: (data: object) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + callback(event.data.message.data); + } + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + callback(event.data.message.messageType, event.data.message.data); + } + }); + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve) => { + const handler = (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + window.removeEventListener("message", handler); + resolve(event.data.message.data); + } + } + }; + window.addEventListener("message", handler); + this.send(messageType, data); + }); + } + + onOpen(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingOpen") { + callback(); + } + }); + } + onClose(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingClose") { + callback(); + } + }); + } +} diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx index a9c69c5b..02c9ff31 100644 --- a/extension/react-app/src/tabs/notebook.tsx +++ b/extension/react-app/src/tabs/notebook.tsx @@ -14,6 +14,7 @@ import StepContainer from "../components/StepContainer"; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import useContinueWebsocket from "../hooks/useWebsocket"; +import useContinueNotebookProtocol from "../hooks/useWebsocket"; let TopNotebookDiv = styled.div` display: grid; @@ -33,8 +34,6 @@ interface NotebookProps { } function Notebook(props: NotebookProps) { - const serverUrl = useSelector((state: RootStore) => state.config.apiUrl); - const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [history, setHistory] = useState(); @@ -157,30 +156,17 @@ function Notebook(props: NotebookProps) { // } as any // ); - const { send: websocketSend } = useContinueWebsocket(serverUrl, (msg) => { - let data = JSON.parse(msg.data); - if (data.messageType === "state") { - setWaitingForSteps(data.state.active); - setHistory(data.state.history); - setUserInputQueue(data.state.user_input_queue); - } - }); + const client = useContinueNotebookProtocol(); - // useEffect(() => { - // (async () => { - // if (sessionId && props.firstObservation) { - // let resp = await fetch(serverUrl + "/observation", { - // method: "POST", - // headers: new Headers({ - // "x-continue-session-id": sessionId, - // }), - // body: JSON.stringify({ - // observation: props.firstObservation, - // }), - // }); - // } - // })(); - // }, [props.firstObservation]); + useEffect(() => { + console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); + client?.onStateUpdate((state) => { + console.log("Received state update: ", state); + setWaitingForSteps(state.active); + setHistory(state.history); + setUserInputQueue(state.user_input_queue); + }); + }, [client]); const mainTextInputRef = useRef(null); @@ -201,14 +187,12 @@ function Notebook(props: NotebookProps) { const onMainTextInput = () => { if (mainTextInputRef.current) { - let value = mainTextInputRef.current.value; + if (!client) return; + let input = mainTextInputRef.current.value; setWaitingForSteps(true); - websocketSend({ - messageType: "main_input", - value: value, - }); + client.sendMainInput(input); setUserInputQueue((queue) => { - return [...queue, value]; + return [...queue, input]; }); mainTextInputRef.current.value = ""; mainTextInputRef.current.style.height = ""; @@ -216,17 +200,20 @@ function Notebook(props: NotebookProps) { }; const onStepUserInput = (input: string, index: number) => { + if (!client) return; console.log("Sending step user input", input, index); - websocketSend({ - messageType: "step_user_input", - value: input, - index, - }); + client.sendStepUserInput(input, index); }; // const iterations = useSelector(selectIterations); return ( + {typeof client === "undefined" && ( + <> + +

Server disconnected

+ + )} {history?.timeline.map((node: HistoryNode, index: number) => { return ( history?.current_index} historyNode={node} onRefinement={(input: string) => { - websocketSend({ - messageType: "refinement_input", - value: input, - index, - }); + client?.sendRefinementInput(input, index); }} onReverse={() => { - websocketSend({ - messageType: "reverse", - index, - }); + client?.reverseToIndex(index); }} /> ); diff --git a/extension/react-app/src/vscode/index.ts b/extension/react-app/src/vscode/index.ts index 7e373cd9..0785aa4d 100644 --- a/extension/react-app/src/vscode/index.ts +++ b/extension/react-app/src/vscode/index.ts @@ -5,6 +5,7 @@ declare const vscode: any; export function postVscMessage(type: string, data: any) { if (typeof vscode === "undefined") { + console.log("Unable to send message: vscode is undefined"); return; } vscode.postMessage({ diff --git a/extension/react-app/tsconfig.json b/extension/react-app/tsconfig.json index 3d0a51a8..940a9359 100644 --- a/extension/react-app/tsconfig.json +++ b/extension/react-app/tsconfig.json @@ -16,6 +16,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"], + "include": ["src", "../src/util/messenger.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index d1483db9..2019c904 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index a0aa560b..712ffe13 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -10,7 +10,7 @@ import { getContinueServerUrl } from "../bridge"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; -export let ideProtocolClient: IdeProtocolClient | undefined = undefined; +export let ideProtocolClient: IdeProtocolClient; export function activateExtension( context: vscode.ExtensionContext, @@ -24,7 +24,7 @@ export function activateExtension( let serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( - serverUrl.replace("http", "ws") + "/ide/ws", + `${serverUrl.replace("http", "ws")}/ide/ws`, context ); diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 93a471ff..ad6ac71b 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -28,18 +28,7 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> { return [stdout, stderr]; } -async function getPythonCmdAssumingInstalled() { - const [, stderr] = await runCommand("python3 --version"); - if (stderr) { - return "python"; - } - return "python3"; -} - -async function setupPythonEnv() { - console.log("Setting up python env for Continue extension..."); - // First check that python3 is installed - +async function getPythonPipCommands() { var [stdout, stderr] = await runCommand("python3 --version"); let pythonCmd = "python3"; if (stderr) { @@ -58,28 +47,77 @@ async function setupPythonEnv() { } } let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; + return [pythonCmd, pipCmd]; +} +function getActivateUpgradeCommands(pythonCmd: string, pipCmd: string) { let activateCmd = ". env/bin/activate"; let pipUpgradeCmd = `${pipCmd} install --upgrade pip`; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; pipUpgradeCmd = `${pythonCmd} -m pip install --upgrade pip`; } + return [activateCmd, pipUpgradeCmd]; +} - let command = `cd ${path.join( +function checkEnvExists() { + const envBinActivatePath = path.join( getExtensionUri().fsPath, - "scripts" - )} && ${pythonCmd} -m venv env && ${activateCmd} && ${pipUpgradeCmd} && ${pipCmd} install -r requirements.txt`; - var [stdout, stderr] = await runCommand(command); - if (stderr) { - throw new Error(stderr); + "scripts", + "env", + "bin", + "activate" + ); + return fs.existsSync(envBinActivatePath); +} + +async function setupPythonEnv() { + console.log("Setting up python env for Continue extension..."); + + // Assemble the command to create the env + const [pythonCmd, pipCmd] = await getPythonPipCommands(); + const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( + pythonCmd, + pipCmd + ); + const createEnvCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + `${pythonCmd} -m venv env`, + ].join(" && "); + + // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) + while (true) { + const [, stderr] = await runCommand(createEnvCommand); + if (stderr) { + throw new Error(stderr); + } + if (checkEnvExists()) { + break; + } else { + // Remove the env and try again + const removeCommand = `rm -rf ${path.join( + getExtensionUri().fsPath, + "scripts", + "env" + )}`; + await runCommand(removeCommand); + } } console.log( "Successfully set up python env at ", getExtensionUri().fsPath + "/scripts/env" ); - await startContinuePythonServer(); + const installRequirementsCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + activateCmd, + pipUpgradeCmd, + `${pipCmd} install -r requirements.txt`, + ].join(" && "); + const [, stderr] = await runCommand(installRequirementsCommand); + if (stderr) { + throw new Error(stderr); + } } function readEnvFile(path: string) { @@ -116,29 +154,19 @@ function writeEnvFile(path: string, key: string, value: string) { } export async function startContinuePythonServer() { + await setupPythonEnv(); + // Check vscode settings let serverUrl = getContinueServerUrl(); if (serverUrl !== "http://localhost:8000") { return; } - let envFile = path.join(getExtensionUri().fsPath, "scripts", ".env"); - let openai_api_key: string | undefined = - readEnvFile(envFile)["OPENAI_API_KEY"]; - while (typeof openai_api_key === "undefined" || openai_api_key === "") { - openai_api_key = await vscode.window.showInputBox({ - prompt: "Enter your OpenAI API key", - placeHolder: "Enter your OpenAI API key", - }); - // Write to .env file - } - writeEnvFile(envFile, "OPENAI_API_KEY", openai_api_key); - console.log("Starting Continue python server..."); // Check if already running by calling /health try { - let response = await fetch(serverUrl + "/health"); + const response = await fetch(serverUrl + "/health"); if (response.status === 200) { console.log("Continue python server already running"); return; @@ -152,15 +180,18 @@ export async function startContinuePythonServer() { pythonCmd = "python"; } + // let command = `cd ${path.join( + // getExtensionUri().fsPath, + // "scripts" + // )} && ${activateCmd} && cd env/lib/python3.11/site-packages && ${pythonCmd} -m continuedev.server.main`; let command = `cd ${path.join( getExtensionUri().fsPath, "scripts" )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; try { // exec(command); - let child = spawn(command, { + const child = spawn(command, { shell: true, - detached: true, }); child.stdout.on("data", (data: any) => { console.log(`stdout: ${data}`); @@ -194,11 +225,6 @@ export function isPythonEnvSetup(): boolean { return fs.existsSync(path.join(pathToEnvCfg)); } -export async function setupExtensionEnvironment() { - console.log("Setting up environment for Continue extension..."); - await Promise.all([setupPythonEnv()]); -} - export async function downloadPython3() { // Download python3 and return the command to run it (python or python3) let os = process.platform; diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 18f08e31..aeeb4b4f 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -62,11 +62,11 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, "continue.openDebugPanel": () => { - ideProtocolClient?.openNotebook(); + ideProtocolClient.openNotebook(); }, "continue.focusContinueInput": async () => { if (!debugPanelWebview) { - await ideProtocolClient?.openNotebook(); + await ideProtocolClient.openNotebook(); } debugPanelWebview?.postMessage({ type: "focusContinueInput", diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 35eb668d..477d1420 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -10,30 +10,28 @@ import { } from "./suggestions"; import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; -const util = require("util"); -const exec = util.promisify(require("child_process").exec); -const WebSocket = require("ws"); import fs = require("fs"); +import { WebsocketMessenger } from "./util/messenger"; class IdeProtocolClient { - private _ws: WebSocket | null = null; - private _panels: Map = new Map(); - private readonly _serverUrl: string; - private readonly _context: vscode.ExtensionContext; + private messenger: WebsocketMessenger | null = null; + private panels: Map = new Map(); + private readonly context: vscode.ExtensionContext; private _makingEdit = 0; constructor(serverUrl: string, context: vscode.ExtensionContext) { - this._context = context; - this._serverUrl = serverUrl; - let ws = new WebSocket(serverUrl); - this._ws = ws; - ws.onclose = () => { - this._ws = null; - }; - ws.on("message", (data: any) => { - this.handleMessage(JSON.parse(data)); + this.context = context; + + let messenger = new WebsocketMessenger(serverUrl); + this.messenger = messenger; + messenger.onClose(() => { + this.messenger = null; + }); + messenger.onMessage((messageType, data) => { + this.handleMessage(messageType, data); }); + // Setup listeners for any file changes in open editors vscode.workspace.onDidChangeTextDocument((event) => { if (this._makingEdit === 0) { @@ -58,125 +56,52 @@ class IdeProtocolClient { }; } ); - this.send("fileEdits", { fileEdits }); + this.messenger?.send("fileEdits", { fileEdits }); } else { this._makingEdit--; } }); } - async isConnected() { - if (this._ws === null || this._ws.readyState !== WebSocket.OPEN) { - let ws = new WebSocket(this._serverUrl); - ws.onclose = () => { - this._ws = null; - }; - ws.on("message", (data: any) => { - this.handleMessage(JSON.parse(data)); - }); - this._ws = ws; - - return new Promise((resolve, reject) => { - ws.addEventListener("open", () => { - resolve(null); - }); - }); - } - } - - async startCore() { - var { stdout, stderr } = await exec( - "cd /Users/natesesti/Desktop/continue/continue && poetry shell" - ); - if (stderr) { - throw new Error(stderr); - } - var { stdout, stderr } = await exec( - "cd .. && uvicorn continue.src.server.main:app --reload --reload-dir continue" - ); - if (stderr) { - throw new Error(stderr); - } - var { stdout, stderr } = await exec("python3 -m continue.src.libs.ide"); - if (stderr) { - throw new Error(stderr); - } - } - - async send(messageType: string, data: object) { - await this.isConnected(); - let msg = JSON.stringify({ messageType, ...data }); - this._ws!.send(msg); - console.log("Sent message", msg); - } - - async receiveMessage(messageType: string): Promise { - await this.isConnected(); - console.log("Connected to websocket"); - return await new Promise((resolve, reject) => { - if (!this._ws) { - reject("Not connected to websocket"); - } - this._ws!.onmessage = (event: any) => { - let message = JSON.parse(event.data); - console.log("RECEIVED MESSAGE", message); - if (message.messageType === messageType) { - resolve(message); - } - }; - }); - } - - async sendAndReceive(message: any, messageType: string): Promise { - try { - await this.send(messageType, message); - let msg = await this.receiveMessage(messageType); - console.log("Received message", msg); - return msg; - } catch (e) { - console.log("Error sending message", e); - } - } - - async handleMessage(message: any) { - switch (message.messageType) { + async handleMessage(messageType: string, data: any) { + switch (messageType) { case "highlightedCode": - this.send("highlightedCode", { + this.messenger?.send("highlightedCode", { highlightedCode: this.getHighlightedCode(), }); break; case "workspaceDirectory": - this.send("workspaceDirectory", { + this.messenger?.send("workspaceDirectory", { workspaceDirectory: this.getWorkspaceDirectory(), }); case "openFiles": - this.send("openFiles", { + this.messenger?.send("openFiles", { openFiles: this.getOpenFiles(), }); break; case "readFile": - this.send("readFile", { - contents: this.readFile(message.filepath), + this.messenger?.send("readFile", { + contents: this.readFile(data.filepath), }); break; case "editFile": - let fileEdit = await this.editFile(message.edit); - this.send("editFile", { + const fileEdit = await this.editFile(data.edit); + this.messenger?.send("editFile", { fileEdit, }); break; case "saveFile": - this.saveFile(message.filepath); + this.saveFile(data.filepath); break; case "setFileOpen": - this.openFile(message.filepath); + this.openFile(data.filepath); // TODO: Close file break; case "openNotebook": case "connected": break; default: - throw Error("Unknown message type:" + message.messageType); + throw Error("Unknown message type:" + messageType); } } getWorkspaceDirectory() { @@ -209,17 +134,20 @@ class IdeProtocolClient { // Initiate Request closeNotebook(sessionId: string) { - this._panels.get(sessionId)?.dispose(); - this._panels.delete(sessionId); + this.panels.get(sessionId)?.dispose(); + this.panels.delete(sessionId); } async openNotebook() { console.log("OPENING NOTEBOOK"); - let resp = await this.sendAndReceive({}, "openNotebook"); - let sessionId = resp.sessionId; + if (this.messenger === null) { + console.log("MESSENGER IS NULL"); + } + const resp = await this.messenger?.sendAndReceive("openNotebook", {}); + const sessionId = resp.sessionId; console.log("SESSION ID", sessionId); - let column = getRightViewColumn(); + const column = getRightViewColumn(); const panel = vscode.window.createWebviewPanel( "continue.debugPanelView", "Continue", @@ -231,9 +159,9 @@ class IdeProtocolClient { ); // And set its HTML content - panel.webview.html = setupDebugPanel(panel, this._context, sessionId); + panel.webview.html = setupDebugPanel(panel, this.context, sessionId); - this._panels.set(sessionId, panel); + this.panels.set(sessionId, panel); } acceptRejectSuggestion(accept: boolean, key: SuggestionRanges) { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 4192595c..a295085f 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,7 +16,6 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; -const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -108,15 +107,15 @@ class WebsocketConnection { this._onOpen = onOpen; this._onClose = onClose; - this._ws.onmessage = (event) => { + this._ws.addEventListener("message", (event) => { this._onMessage(event.data); - }; - this._ws.onclose = () => { + }); + this._ws.addEventListener("close", () => { this._onClose(); - }; - this._ws.onopen = () => { + }); + this._ws.addEventListener("open", () => { this._onOpen(); - }; + }); } public send(message: string) { @@ -230,6 +229,19 @@ export function setupDebugPanel( apiUrl: getContinueServerUrl(), sessionId, }); + + // // Listen for changes to server URL in settings + // vscode.workspace.onDidChangeConfiguration((event) => { + // if (event.affectsConfiguration("continue.serverUrl")) { + // debugPanelWebview?.postMessage({ + // type: "onLoad", + // vscMachineId: vscode.env.machineId, + // apiUrl: getContinueServerUrl(), + // sessionId, + // }); + // } + // }); + break; } diff --git a/extension/src/extension.ts b/extension/src/extension.ts index e0b94278..88af0d19 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -4,7 +4,6 @@ import * as vscode from "vscode"; import { - setupExtensionEnvironment, isPythonEnvSetup, startContinuePythonServer, } from "./activation/environmentSetup"; @@ -26,11 +25,7 @@ export function activate(context: vscode.ExtensionContext) { cancellable: false, }, async () => { - if (isPythonEnvSetup()) { - await startContinuePythonServer(); - } else { - await setupExtensionEnvironment(); - } + await startContinuePythonServer(); dynamicImportAndActivate(context, true); } ); diff --git a/extension/src/test/runTest.ts b/extension/src/test/runTest.ts index 27b3ceb2..e810ed5b 100644 --- a/extension/src/test/runTest.ts +++ b/extension/src/test/runTest.ts @@ -1,23 +1,23 @@ -import * as path from 'path'; +import * as path from "path"; -import { runTests } from '@vscode/test-electron'; +import { runTests } from "@vscode/test-electron"; async function main() { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); - // The path to test runner - // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './suite/index'); + // The path to test runner + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); - } catch (err) { - console.error('Failed to run tests'); - process.exit(1); - } + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } } main(); diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts new file mode 100644 index 00000000..6f8bb29d --- /dev/null +++ b/extension/src/util/messenger.ts @@ -0,0 +1,108 @@ +console.log("Websocket import"); +const WebSocket = require("ws"); + +export abstract class Messenger { + abstract send(messageType: string, data: object): void; + + abstract onMessageType( + messageType: string, + callback: (data: object) => void + ): void; + + abstract onMessage(callback: (messageType: string, data: any) => void): void; + + abstract onOpen(callback: () => void): void; + + abstract onClose(callback: () => void): void; + + abstract sendAndReceive(messageType: string, data: any): Promise; +} + +export class WebsocketMessenger extends Messenger { + websocket: WebSocket; + private onMessageListeners: { + [messageType: string]: ((data: object) => void)[]; + } = {}; + private onOpenListeners: (() => void)[] = []; + private onCloseListeners: (() => void)[] = []; + private serverUrl: string; + + _newWebsocket(): WebSocket { + // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser. + // if (typeof process === "object") { + // console.log("Using node"); + // // process is only available in Node + // var WebSocket = require("ws"); + // } + + const newWebsocket = new WebSocket(this.serverUrl); + for (const listener of this.onOpenListeners) { + this.onOpen(listener); + } + for (const listener of this.onCloseListeners) { + this.onClose(listener); + } + for (const messageType in this.onMessageListeners) { + for (const listener of this.onMessageListeners[messageType]) { + this.onMessageType(messageType, listener); + } + } + return newWebsocket; + } + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + this.websocket = this._newWebsocket(); + } + + send(messageType: string, data: object) { + const payload = JSON.stringify({ messageType, data }); + if (this.websocket.readyState === this.websocket.OPEN) { + this.websocket.send(payload); + } else { + if (this.websocket.readyState !== this.websocket.CONNECTING) { + this.websocket = this._newWebsocket(); + } + this.websocket.addEventListener("open", () => { + this.websocket.send(payload); + }); + } + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve, reject) => { + const eventListener = (data: any) => { + // THIS ISN"T GETTING CALLED + resolve(data); + this.websocket.removeEventListener("message", eventListener); + }; + this.onMessageType(messageType, eventListener); + this.send(messageType, data); + }); + } + + onMessageType(messageType: string, callback: (data: any) => void): void { + this.websocket.addEventListener("message", (event: any) => { + const msg = JSON.parse(event.data); + if (msg.messageType === messageType) { + callback(msg.data); + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + this.websocket.addEventListener("message", (event) => { + const msg = JSON.parse(event.data); + callback(msg.messageType, msg.data); + }); + } + + onOpen(callback: () => void): void { + this.websocket.addEventListener("open", callback); + } + + onClose(callback: () => void): void { + this.websocket.addEventListener("close", callback); + } +} -- cgit v1.2.3-70-g09d2 From a4e9e7764ee42a743dbfbaedb520cc70daa23ec4 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 20:27:52 -0400 Subject: fixing up vscodemessenger --- continuedev/src/continuedev/server/main.py | 3 +- continuedev/src/continuedev/server/notebook.py | 14 ++--- .../src/continuedev/server/session_manager.py | 15 ------ extension/package-lock.json | 4 +- extension/package.json | 4 +- .../src/hooks/useContinueNotebookProtocol.ts | 2 +- extension/react-app/src/hooks/useWebsocket.ts | 2 +- extension/react-app/src/hooks/vscodeMessenger.ts | 14 +++-- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 56070 -> 56086 bytes extension/src/activation/environmentSetup.ts | 59 +++++++++++---------- extension/src/debugPanel.ts | 5 ++ logging.ini | 27 ---------- logging.yaml | 30 +++++++++++ 13 files changed, 91 insertions(+), 88 deletions(-) delete mode 100644 logging.ini create mode 100644 logging.yaml (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index e87d5fa9..1977bfdd 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -32,7 +32,8 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port) + uvicorn.run(app, host="0.0.0.0", port=args.port, + log_config="logging.yaml") if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index edb61a45..ab9211a8 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -51,14 +51,17 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): def __init__(self, session: Session): self.session = session - async def _send_json(self, data: Any): - await self.websocket.send_json(data) + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "message_type": message_type, + "data": data + }) async def _receive_json(self, message_type: str) -> Any: return await self.sub_queue.get(message_type) async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: - await self._send_json(data) + await self._send_json(message_type, data) resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) @@ -77,8 +80,7 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): async def send_state_update(self): state = self.session.agent.get_full_state().dict() - await self._send_json({ - "messageType": "state_update", + await self._send_json("state_update", { "state": state }) @@ -112,7 +114,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we await protocol.send_state_update() while AppStatus.should_exit is False: - message = await websocket.receive_json() + message = await websocket.receive_text() print("Received message", message) if type(message) is str: message = json.loads(message) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index b48c21b7..c5715034 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -77,25 +77,10 @@ class SessionManager: print(f"Session {session_id} has no websocket") return - async def a(): - await self.sessions[session_id].ws.send_json({ - "messageType": message_type, - "data": data - }) - - # Run coroutine in background await self.sessions[session_id].ws.send_json({ "messageType": message_type, "data": data }) - return - if self._event_loop is None or self._event_loop.is_closed(): - self._event_loop = asyncio.new_event_loop() - self._event_loop.run_until_complete(a()) - self._event_loop.close() - else: - self._event_loop.run_until_complete(a()) - self._event_loop.close() session_manager = SessionManager() diff --git a/extension/package-lock.json b/extension/package-lock.json index 04af09d3..cd956286 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.5", + "version": "0.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.5", + "version": "0.0.7", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index c96655a9..1a8d9004 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.5", + "version": "0.0.7", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -162,7 +162,7 @@ "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.5.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.6.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts index d5ffbf09..b785cc84 100644 --- a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts +++ b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts @@ -10,7 +10,7 @@ class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProto constructor( serverUrlWithSessionId: string, - useVscodeMessagePassing: boolean = false + useVscodeMessagePassing: boolean ) { super(); this.serverUrlWithSessionId = serverUrlWithSessionId; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index b98be577..016fa17d 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -4,7 +4,7 @@ import { useSelector } from "react-redux"; import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; import { postVscMessage } from "../vscode"; -function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = false) { +function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); const [client, setClient] = useState< diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts index 746c4302..e330db57 100644 --- a/extension/react-app/src/hooks/vscodeMessenger.ts +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -21,8 +21,10 @@ export class VscodeMessenger extends Messenger { onMessageType(messageType: string, callback: (data: object) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - if (event.data.message.messageType === messageType) { - callback(event.data.message.data); + console.log("VS CODE SENT DATA: ", event.data); + const data = JSON.parse(event.data.data); + if (data.messageType === messageType) { + callback(data.data); } } }); @@ -31,7 +33,8 @@ export class VscodeMessenger extends Messenger { onMessage(callback: (messageType: string, data: any) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - callback(event.data.message.messageType, event.data.message.data); + const data = JSON.parse(event.data.data); + callback(data.messageType, data.data); } }); } @@ -40,9 +43,10 @@ export class VscodeMessenger extends Messenger { return new Promise((resolve) => { const handler = (event: any) => { if (event.data.type === "websocketForwardingMessage") { - if (event.data.message.messageType === messageType) { + const data = JSON.parse(event.data.data); + if (data.messageType === messageType) { window.removeEventListener("message", handler); - resolve(event.data.message.data); + resolve(data.data); } } }; diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 2019c904..8a35e0ec 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index ad6ac71b..db2c5523 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -10,6 +10,7 @@ import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; async function runCommand(cmd: string): Promise<[string, string | undefined]> { + console.log("Running command: ", cmd); var stdout: any = ""; var stderr: any = ""; try { @@ -61,14 +62,16 @@ function getActivateUpgradeCommands(pythonCmd: string, pipCmd: string) { } function checkEnvExists() { - const envBinActivatePath = path.join( + const envBinPath = path.join( getExtensionUri().fsPath, "scripts", "env", - "bin", - "activate" + "bin" + ); + return ( + fs.existsSync(envBinPath + "/activate") && + fs.existsSync(envBinPath + "/pip") ); - return fs.existsSync(envBinActivatePath); } async function setupPythonEnv() { @@ -180,34 +183,34 @@ export async function startContinuePythonServer() { pythonCmd = "python"; } - // let command = `cd ${path.join( - // getExtensionUri().fsPath, - // "scripts" - // )} && ${activateCmd} && cd env/lib/python3.11/site-packages && ${pythonCmd} -m continuedev.server.main`; let command = `cd ${path.join( getExtensionUri().fsPath, "scripts" )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; - try { - // exec(command); - const child = spawn(command, { - shell: true, - }); - child.stdout.on("data", (data: any) => { - console.log(`stdout: ${data}`); - }); - child.stderr.on("data", (data: any) => { - console.log(`stderr: ${data}`); - }); - child.on("error", (error: any) => { - console.log(`error: ${error.message}`); - }); - } catch (e) { - console.log("Failed to start Continue python server", e); - } - // Sleep for 3 seconds to give the server time to start - await new Promise((resolve) => setTimeout(resolve, 3000)); - console.log("Successfully started Continue python server"); + + return new Promise((resolve, reject) => { + try { + const child = spawn(command, { + shell: true, + }); + child.stdout.on("data", (data: any) => { + console.log(`stdout: ${data}`); + }); + child.stderr.on("data", (data: any) => { + console.log(`stderr: ${data}`); + if (data.includes("Uvicorn running on")) { + console.log("Successfully started Continue python server"); + resolve(null); + } + }); + child.on("error", (error: any) => { + console.log(`error: ${error.message}`); + }); + } catch (e) { + console.log("Failed to start Continue python server", e); + reject(); + } + }); } async function installNodeModules() { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index a295085f..da29a52c 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,6 +16,7 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; +const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -119,6 +120,9 @@ class WebsocketConnection { } public send(message: string) { + if (typeof message !== "string") { + message = JSON.stringify(message); + } this._ws.send(message); } @@ -190,6 +194,7 @@ export function setupDebugPanel( async function connectWebsocket(url: string) { return new Promise((resolve, reject) => { const onMessage = (message: any) => { + console.log("websocket message", message); panel.webview.postMessage({ type: "websocketForwardingMessage", url, diff --git a/logging.ini b/logging.ini deleted file mode 100644 index 5b478619..00000000 --- a/logging.ini +++ /dev/null @@ -1,27 +0,0 @@ -[loggers] -keys=root - -[handlers] -keys=logfile,logconsole - -[formatters] -keys=logformatter - -[logger_root] -level=INFO -handlers=logfile, logconsole - -[formatter_logformatter] -format=[%(asctime)s.%(msecs)03d] %(levelname)s [%(thread)d] - %(message)s - -[handler_logfile] -class=handlers.RotatingFileHandler -level=INFO -args=('logfile.log','a') -formatter=logformatter - -[handler_logconsole] -class=handlers.logging.StreamHandler -level=INFO -args=() -formatter=logformatter \ No newline at end of file diff --git a/logging.yaml b/logging.yaml new file mode 100644 index 00000000..391041ef --- /dev/null +++ b/logging.yaml @@ -0,0 +1,30 @@ +version: 1 +disable_existing_loggers: False +formatters: + default: + (): 'uvicorn.logging.DefaultFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(message)s' + access: + (): 'uvicorn.logging.AccessFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + class: logging.StreamHandler + formatter: default + stream: ext://sys.stderr + access: + class: logging.StreamHandler + formatter: access + stream: ext://sys.stdout +loggers: + uvicorn: + level: INFO + handlers: + - default + uvicorn.error: + level: INFO + uvicorn.access: + level: INFO + propagate: False + handlers: + - access \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ea5d50af9ba84242c25e82069d86c08ac039e543 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 00:09:19 -0400 Subject: Polishing for dlt codespace and !config! --- continuedev/src/continuedev/core/config.py | 29 ++++++++++++++++++++ continuedev/src/continuedev/core/policy.py | 9 +++++-- continuedev/src/continuedev/core/sdk.py | 17 ++++++++++++ .../src/continuedev/libs/steps/draft/dlt.py | 4 +-- continuedev/src/continuedev/libs/steps/main.py | 20 ++++++++++++++ .../src/continuedev/libs/steps/steps_on_startup.py | 30 +++++++++++++++++++++ continuedev/src/continuedev/server/main.py | 8 ++++-- continuedev/src/continuedev/server/notebook.py | 2 +- extension/react-app/src/hooks/vscodeMessenger.ts | 1 - .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 56086 -> 57363 bytes extension/src/activation/activate.ts | 6 ++--- extension/src/activation/environmentSetup.ts | 2 ++ 12 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 continuedev/src/continuedev/core/config.py create mode 100644 continuedev/src/continuedev/libs/steps/steps_on_startup.py (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py new file mode 100644 index 00000000..e62f0e4f --- /dev/null +++ b/continuedev/src/continuedev/core/config.py @@ -0,0 +1,29 @@ +import json +import os +from pydantic import BaseModel +from typing import List, Optional, Dict +import yaml + + +class ContinueConfig(BaseModel): + """ + A pydantic class for the continue config file. + """ + steps_on_startup: Optional[Dict[str, Dict]] = {} + server_url: Optional[str] = None + + +def load_config(config_file: str) -> ContinueConfig: + """ + Load the config file and return a ContinueConfig object. + """ + _, ext = os.path.splitext(config_file) + if ext == '.yaml': + with open(config_file, 'r') as f: + config_dict = yaml.safe_load(f) + elif ext == '.json': + with open(config_file, 'r') as f: + config_dict = json.load(f) + else: + raise ValueError(f'Unknown config file extension: {ext}') + return ContinueConfig(**config_dict) diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index c0ba0f4f..9f68515f 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,9 +1,10 @@ from typing import List, Tuple, Type -from ..libs.steps.ty import CreatePipelineStep +from ..libs.steps.steps_on_startup import StepsOnStartupStep +from ..libs.steps.draft.dlt import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep # from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -13,6 +14,10 @@ class DemoPolicy(Policy): ran_code_last: bool = False def next(self, history: History) -> Step: + # At the very start, run initial Steps spcecified in the config + if history.get_current() is None: + return MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() + observation = history.last_observation() if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 6ae0be04..4d82a1ae 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,5 +1,7 @@ import os from typing import Coroutine, Union + +from .config import ContinueConfig, load_config from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM @@ -106,3 +108,18 @@ class ContinueSDK: val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text save_env_var(env_var, val) return val + + async def get_config(self) -> ContinueConfig: + dir = await self.ide.getWorkspaceDirectory() + yaml_path = os.path.join(dir, 'continue.yaml') + json_path = os.path.join(dir, 'continue.json') + if os.path.exists(yaml_path): + return load_config(yaml_path) + elif os.path.exists(json_path): + return load_config(json_path) + else: + return ContinueConfig() + + def set_loading_message(self, message: str): + # self.__agent.set_loading_message(message) + raise NotImplementedError() diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 460aa0cc..778ced1d 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -2,7 +2,7 @@ from textwrap import dedent from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK -from ..main import WaitForUserInputStep +from ..core.core import WaitForUserInputStep class SetupPipelineStep(Step): @@ -77,6 +77,6 @@ class CreatePipelineStep(Step): async def run(self, sdk: ContinueSDK): await sdk.run_step( WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep() >> + SetupPipelineStep(api_description="WeatherAPI.com API") >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 70c0d4b8..d31db0eb 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -331,3 +331,23 @@ class SolveTracebackStep(Step): await sdk.run_step(EditCodeStep( range_in_files=range_in_files, prompt=prompt)) return None + + +class MessageStep(Step): + message: str + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.message + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + pass + + +class EmptyStep(Step): + hide: bool = True + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return "" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + pass diff --git a/continuedev/src/continuedev/libs/steps/steps_on_startup.py b/continuedev/src/continuedev/libs/steps/steps_on_startup.py new file mode 100644 index 00000000..fd1eb8f0 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/steps_on_startup.py @@ -0,0 +1,30 @@ + + +from ...core.main import ContinueSDK, Models, Step +from .main import UserInputStep +from .draft.dlt import CreatePipelineStep + + +step_name_to_step_class = { + "UserInputStep": UserInputStep, + "CreatePipelineStep": CreatePipelineStep +} + + +class StepsOnStartupStep(Step): + hide: bool = True + + async def describe(self, models: Models): + return "Running steps on startup" + + async def run(self, sdk: ContinueSDK): + steps_descriptions = (await sdk.get_config()).steps_on_startup + + for step_name, step_params in steps_descriptions.items(): + try: + step = step_name_to_step_class[step_name](**step_params) + except: + print( + f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") + continue + await sdk.run_step(step) diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 1977bfdd..1ffe1450 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -1,3 +1,4 @@ +import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .ide import router as ide_router @@ -32,8 +33,11 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port, - log_config="logging.yaml") + if os.path.exists("logging.yaml"): + uvicorn.run(app, host="0.0.0.0", port=args.port, + log_config="logging.yaml") + else: + uvicorn.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index ab9211a8..9ca510dd 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -53,7 +53,7 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): async def _send_json(self, message_type: str, data: Any): await self.websocket.send_json({ - "message_type": message_type, + "messageType": message_type, "data": data }) diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts index e330db57..ba19586b 100644 --- a/extension/react-app/src/hooks/vscodeMessenger.ts +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -21,7 +21,6 @@ export class VscodeMessenger extends Messenger { onMessageType(messageType: string, callback: (data: object) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - console.log("VS CODE SENT DATA: ", event.data); const data = JSON.parse(event.data.data); if (data.messageType === messageType) { callback(data.data); diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 8a35e0ec..6cda6f2d 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 712ffe13..f8f3c65a 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -62,9 +62,9 @@ export function activateExtension( ideProtocolClient?.openNotebook(); }); } else { - // ideProtocolClient?.openNotebook().then(() => { - // // openCapturedTerminal(); - // }); + ideProtocolClient.openNotebook().then(() => { + // openCapturedTerminal(); + }); } extensionContext = context; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index db2c5523..419d32ed 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -77,6 +77,8 @@ function checkEnvExists() { async function setupPythonEnv() { console.log("Setting up python env for Continue extension..."); + if (checkEnvExists()) return; + // Assemble the command to create the env const [pythonCmd, pipCmd] = await getPythonPipCommands(); const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( -- cgit v1.2.3-70-g09d2 From 5ce90853586fcfaa7d795e2593aaaecce4bbed49 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 19:03:10 -0400 Subject: polishing dlt recipe --- continuedev/src/continuedev/core/agent.py | 13 ++++++++---- .../src/continuedev/libs/steps/core/core.py | 3 ++- .../src/continuedev/libs/steps/draft/dlt.py | 24 ++++++++++++++++++++-- continuedev/src/continuedev/libs/steps/main.py | 1 + .../react-app/src/components/StepContainer.tsx | 2 ++ extension/react-app/src/components/index.ts | 2 +- extension/src/activation/environmentSetup.ts | 6 +++--- 7 files changed, 40 insertions(+), 11 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 0dba6122..cf5c9781 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -10,6 +10,7 @@ from ..models.main import ContinueBaseModel from .main import Policy, History, FullState, Step, HistoryNode from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep from .sdk import ContinueSDK +import asyncio class Agent(ContinueBaseModel): @@ -88,6 +89,9 @@ class Agent(ContinueBaseModel): self.history.add_node(HistoryNode( step=step, observation=None, depth=self._step_depth)) + # Call all subscribed callbacks + await self.update_subscribers() + # Run step self._step_depth += 1 observation = await step(ContinueSDK(self)) @@ -98,10 +102,11 @@ class Agent(ContinueBaseModel): self._step_depth, include_current=True).observation = observation # Update its description - step._set_description(await step.describe(ContinueSDK(self).models)) - - # Call all subscribed callbacks - await self.update_subscribers() + async def update_description(): + step._set_description(await step.describe(ContinueSDK(self).models)) + # Update subscribers with new description + await self.update_subscribers() + asyncio.create_task(update_description()) return observation diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 1761cbfd..9a5d54f0 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -44,7 +44,8 @@ class ShellCommandsStep(Step): name: str = "Run Shell Commands" async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "\n".join(self.cmds) + cmds_str = "\n".join(self.cmds) + return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 94ca2323..73762327 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,16 +1,27 @@ from textwrap import dedent +from ....core.sdk import Models + from ....core.observation import DictObservation from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK from ..core.core import WaitForUserInputStep +from ..main import MessageStep class SetupPipelineStep(Step): + hide: bool = True + name: str = "Setup dlt Pipeline" api_description: str # e.g. "I want to load data from the weatherapi.com API" + async def describe(self, models: Models): + return dedent(f"""\ + This step will create a new dlt pipeline that loads data from an API, as per your request: + {self.api_description} + """) + async def run(self, sdk: ContinueSDK): source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() @@ -34,12 +45,11 @@ class SetupPipelineStep(Step): # wait for user to put API key in secrets.toml await sdk.ide.setFileOpen(await sdk.ide.getWorkspaceDirectory() + "/.dlt/secrets.toml") - await sdk.wait_for_user_confirmation("Please add the API key to the `secrets.toml` file and then press `Continue`") + await sdk.wait_for_user_confirmation("If this service requires an API key, please add it to the `secrets.toml` file and then press `Continue`") return DictObservation(values={"source_name": source_name}) class ValidatePipelineStep(Step): - async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] filename = f'{source_name}.py' @@ -77,9 +87,19 @@ class ValidatePipelineStep(Step): class CreatePipelineStep(Step): + hide: bool = True async def run(self, sdk: ContinueSDK): await sdk.run_step( + MessageStep(message=dedent("""\ + This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: + - Create a Python virtual environment with dlt installed + - Run `dlt init` to generate a pipeline template + - Write the code to call the API + - Add any required API keys to the `secrets.toml` file + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> ValidatePipelineStep() diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 6a7f14c7..c70d5c2c 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -334,6 +334,7 @@ class SolveTracebackStep(Step): class MessageStep(Step): + name: str = "Message" message: str async def describe(self, models: Models) -> Coroutine[str, None, None]: diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 36b3d99a..ec601ea7 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -36,6 +36,8 @@ const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` animation: ${appear} 0.3s ease-in-out; /* padding-left: ${(props) => props.stepDepth * 20}px; */ overflow: hidden; + margin-left: 0px; + margin-right: 0px; `; const StepContainerDiv = styled.div<{ open: boolean }>` diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index e37c97f3..7ba60467 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -45,7 +45,7 @@ export const Pre = styled.pre` border-radius: ${defaultBorderRadius}; padding: 8px; max-height: 150px; - overflow: scroll; + overflow-y: scroll; margin: 0; background-color: ${secondaryDark}; border: none; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 419d32ed..170426e1 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -66,11 +66,11 @@ function checkEnvExists() { getExtensionUri().fsPath, "scripts", "env", - "bin" + process.platform == "win32" ? "Scripts" : "bin" ); return ( - fs.existsSync(envBinPath + "/activate") && - fs.existsSync(envBinPath + "/pip") + fs.existsSync(path.join(envBinPath, "activate")) && + fs.existsSync(path.join(envBinPath, "pip")) ); } -- cgit v1.2.3-70-g09d2 From ef0fcedffc53b5ba455b230b112306a46ee0235f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 20:59:10 -0400 Subject: enter secrets directly into .env file --- continuedev/src/continuedev/core/env.py | 10 ++++-- continuedev/src/continuedev/core/sdk.py | 37 +++++++++++++++------- .../src/continuedev/models/filesystem_edit.py | 4 +++ continuedev/src/continuedev/models/main.py | 8 +++++ .../react-app/src/components/StepContainer.tsx | 8 +++++ 5 files changed, 53 insertions(+), 14 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index edd3297c..2692c348 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -7,11 +7,15 @@ def get_env_var(var_name: str): return os.getenv(var_name) -def save_env_var(var_name: str, var_value: str): +def make_sure_env_exists(): if not os.path.exists('.env'): with open('.env', 'w') as f: - f.write(f'{var_name}="{var_value}"\n') - return + f.write('') + + +def save_env_var(var_name: str, var_value: str): + make_sure_env_exists() + with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index b9b20422..ce0c53fd 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -2,7 +2,7 @@ import os from typing import Coroutine, Union from .config import ContinueConfig, load_config -from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory +from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI @@ -11,7 +11,7 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * -from .env import get_env_var, save_env_var +from .env import get_env_var, make_sure_env_exists class Agent: @@ -29,12 +29,12 @@ class Models: async def starcoder(self): api_key = await self.sdk.get_user_secret( - 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file') return HuggingFaceInferenceAPI(api_key=api_key) async def gpt35(self): api_key = await self.sdk.get_user_secret( - 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + 'OPENAI_API_KEY', 'Please add your OpenAI API key to the .env file') return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") @@ -86,6 +86,12 @@ class ContinueSDK: prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' )) + async def append_to_file(self, filename: str, content: str): + filepath = await self._ensure_absolute_path(filename) + previous_content = await self.ide.readFile(filepath) + file_edit = FileEdit.from_append(filepath, previous_content, content) + await self.ide.applyFileSystemEdit(file_edit) + async def add_file(self, filename: str, content: str | None): return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) @@ -99,14 +105,23 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - try: + make_sure_env_exists() + + val = None + while val is None: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + server_dir = os.getcwd() + env_path = os.path.join(server_dir, ".env") + await self.ide.setFileOpen(env_path) + await self.append_to_file(env_path, f'\n{env_var}=""') + await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) val = get_env_var(env_var) - if val is not None: - return val - except: - pass - val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text - save_env_var(env_var, val) + return val async def get_config(self) -> ContinueConfig: diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py index 7526d4c9..8e74b819 100644 --- a/continuedev/src/continuedev/models/filesystem_edit.py +++ b/continuedev/src/continuedev/models/filesystem_edit.py @@ -37,6 +37,10 @@ class FileEdit(AtomicFileSystemEdit): def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": return FileEdit(filepath=filepath, range=Range.from_shorthand(position.line, position.character, position.line, position.character), replacement=content) + @staticmethod + def from_append(filepath: str, previous_content: str, appended_content: str) -> "FileEdit": + return FileEdit(filepath=filepath, range=Range.from_position(Position.from_end_of_file(previous_content)), replacement=appended_content) + class FileEditWithFullContents(BaseModel): fileEdit: FileEdit diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 1bc51ff1..02c44aae 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -39,6 +39,10 @@ class Position(BaseModel): return Position(line=line, character=character) + @staticmethod + def from_end_of_file(contents: str) -> "Position": + return Position.from_index(contents, len(contents)) + class Range(BaseModel): """A range in a file. 0-indexed.""" @@ -117,6 +121,10 @@ class Range(BaseModel): return Range.from_shorthand(start_line, 0, end_line, len(content_lines[end_line]) - 1) + @staticmethod + def from_position(position: Position) -> "Range": + return Range(start=position, end=position) + class AbstractModel(ABC, BaseModel): @root_validator(pre=True) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index ec601ea7..5e979b34 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -80,6 +80,13 @@ function StepContainer(props: StepContainerProps) { const [open, setOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); const naturalLanguageInputRef = useRef(null); + const userInputRef = useRef(null); + + useEffect(() => { + if (userInputRef?.current) { + userInputRef.current.focus(); + } + }, [userInputRef]); useEffect(() => { if (isHovered) { @@ -136,6 +143,7 @@ function StepContainer(props: StepContainerProps) { {props.historyNode.step.name === "Waiting for user input" && ( { if (e.key === "Enter") { -- cgit v1.2.3-70-g09d2 From 5fb7e9931eef72eeb83405edfade978c556c2689 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 23:47:38 -0400 Subject: Edits and braindumps in docs --- docs/docs/concepts/autopilot.md | 20 ++++-- docs/docs/concepts/core.md | 14 ++-- docs/docs/concepts/gui.md | 15 ++-- docs/docs/concepts/history.md | 4 +- docs/docs/concepts/ide.md | 6 +- docs/docs/concepts/llm.md | 4 +- docs/docs/concepts/policy.md | 4 +- docs/docs/concepts/recipe.md | 4 +- docs/docs/concepts/sdk.md | 6 +- docs/docs/concepts/step.md | 6 +- docs/docs/getting-started.md | 27 +++---- docs/docs/how-continue-works.md | 18 +++-- docs/docs/install.md | 13 ++-- docs/docs/intro.md | 18 +++-- docs/docusaurus.config.js | 80 ++++++++++----------- docs/src/components/HomepageFeatures/index.js | 35 ++++----- docs/static/img/continue-architecture.png | Bin 136666 -> 1023648 bytes extension/README.md | 2 +- .../react-app/src/components/CodeMultiselect.tsx | 2 +- .../react-app/src/components/VSCodeFileLink.tsx | 4 +- extension/react-app/src/tabs/chat/MessageDiv.tsx | 2 +- extension/scripts/README.md | 2 +- extension/src/README.md | 14 ++-- 23 files changed, 153 insertions(+), 147 deletions(-) (limited to 'extension/react-app') diff --git a/docs/docs/concepts/autopilot.md b/docs/docs/concepts/autopilot.md index 5073f7cb..f3ed6fc9 100644 --- a/docs/docs/concepts/autopilot.md +++ b/docs/docs/concepts/autopilot.md @@ -8,13 +8,23 @@ The **autopilot** is the main loop, completing steps and then deciding the next ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +The Autopilot class is the center of Continue. Every step is initiated from the Autopilot, which provides it with a ContinueSDK. + +- Records history +- Allows reversal +- Injects SDK +- Has policy to decide what step to take next +- Accepts user input and acts on it +- Main event loop +- Contains main classes that are provided through the SDK, including LLM, History, IDE + +--- - An autopilot takes user input from the React app - You can see this happening in `server/notebook.py` - It basically queues user inputs, pops off the most recent, runs that as a "UserInputStep", uses its Policy to run other steps until the next step is None, and then pops off the next user input. When nothing left, just waits for more - `Autopilot` contains the - - History - - LLM - - Policy - - IDE \ No newline at end of file + - History + - LLM + - Policy + - IDE diff --git a/docs/docs/concepts/core.md b/docs/docs/concepts/core.md index 766dbca0..e9757b36 100644 --- a/docs/docs/concepts/core.md +++ b/docs/docs/concepts/core.md @@ -3,16 +3,16 @@ **TODO: Better explain in one sentence what this is and what its purpose is** :::info -The **Continue Core** connects the [SDK](./sdk.md) and [GUI](./gui.md) with the [IDE](./ide.md) (i.e. in VS Code, GitHub Codespaces, a web browser text editor, etc), enabling the steps to make changes to your code and accelerate your software development workflows +The **Continue Core** holds the main event loop, responsible for connecting [IDE](./ide.md) (i.e. in VS Code, GitHub Codespaces, a web browser text editor, etc), [SDK](./sdk.md), and [GUI](./gui.md), and deciding which steps to take next. ::: ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +I tried a rewrite of the info box above. The core doesn't really mean that much except for maybe the Autopilot class and the small set of classes in core.py, including History, Step, Policy mostly. Maybe best referred to as a set of abstractions. Autopilot runs the main event loop, basically queueing user input and asking the policy what to do next, and injecting the SDK, and recording history. I suppose you could also say it includes the protocols, in which case you might say "connects IDE and GUI through the SDK", though lots of 3-letter acronyms going on here. Notes below are correct. - The `Core` includes - - IDE protocol - - GUI protocol - - SDK - - Autopilot -- There is a two-way sync between an IDE and the GUI that happens through Core \ No newline at end of file + - IDE protocol + - GUI protocol + - SDK + - Autopilot +- There is a two-way sync between an IDE and the GUI that happens through Core diff --git a/docs/docs/concepts/gui.md b/docs/docs/concepts/gui.md index 7ba16a63..ff99839f 100644 --- a/docs/docs/concepts/gui.md +++ b/docs/docs/concepts/gui.md @@ -2,20 +2,17 @@ **TODO: Make sure codebase aligns with this terminology** -**TODO: Explain in one sentence what this is and what its purpose is** - :::info -The **Continue GUI** enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly +The **Continue GUI** lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. ::: - ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +GUI displays every step taken by Continue in a way that lets you easily review, reverse, refine, re-run. Provides a natural language prompt where you can request edits in natural language or initiate recipes with slash commands. Communicates with the Continue server via GUI Protocol. Is a React app, which will eventually be published as a standalone npm package, importable as a simple React component. - **From GUI to Core** - - Natural language instructions from the developer - - Hover / clicked on a step - - Other user input + - Natural language instructions from the developer + - Hover / clicked on a step + - Other user input - **From Core to GUI** - - Updates to state (e.g. a new step) \ No newline at end of file + - Updates to state (e.g. a new step) diff --git a/docs/docs/concepts/history.md b/docs/docs/concepts/history.md index bf72d2b4..7f028e14 100644 --- a/docs/docs/concepts/history.md +++ b/docs/docs/concepts/history.md @@ -8,6 +8,6 @@ The **history** is the ordered record of all past steps ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +History stores a list ("timeline") of HistoryNodes. Each HistoryNode contains the Step with parameters, the Observation returned by the step, and the depth of the step (just so we can understand the tree structure, and what called what, and display as a tree if we wanted). History has a current_index, which can be smaller than the length of the timeline if some steps have been reversed (in which case GUI displays them with lower opacity). History class mostly responsible for maintaining order of history during reversals, grabbing most recent steps, or other things that should be bottlenecked through reliable access methods. -- What step data and metadata is stored in the history? \ No newline at end of file +- What step data and metadata is stored in the history? diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md index f7ff2429..dc20518e 100644 --- a/docs/docs/concepts/ide.md +++ b/docs/docs/concepts/ide.md @@ -8,10 +8,10 @@ The **IDE** is the text editor where you manually edit your code. ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +SDK provides "IDEProtocol" class so that steps can interact with VS Code, etc... in an IDE-agnostic way. Communicates with editor through websockets protocol. All that's needed to make continue work with a new IDE/editor is to implement the protocol on the side of the editor. - `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `notebook.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app -- extension directory contains 1. The VS Code extension, whose code is in `extension/src`, with `extension.ts` being the entry point, and 2. the Continue React app, in the `extension/react-app` folder. This is displayed in the sidebar of VSCode, but is designed to work with any IDE that implements the protocol as is done in `extension/src/continueIdeClient.ts`. +- extension directory contains 1. The VS Code extension, whose code is in `extension/src`, with `extension.ts` being the entry point, and 2. the Continue React app, in the `extension/react-app` folder. This is displayed in the sidebar of VS Code, but is designed to work with any IDE that implements the protocol as is done in `extension/src/continueIdeClient.ts`. ## Supported IDEs @@ -95,4 +95,4 @@ Apply a file edit ### saveFile -Save a file \ No newline at end of file +Save a file diff --git a/docs/docs/concepts/llm.md b/docs/docs/concepts/llm.md index fb3b2a33..6e5fccc1 100644 --- a/docs/docs/concepts/llm.md +++ b/docs/docs/concepts/llm.md @@ -8,7 +8,7 @@ An **LLM** is short for Large Language Model, which includes models like GPT-4, ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +Just a class with a "complete" method. Right now have HuggingFaceInferenceAPI and OpenAI subclasses. Need credentials as of now. Different models useful in different places, so we provide easy access to multiple of them, right now just gpt3.5 and starcoder, but can add others super easily. - `LLM` is the large language model that can be used in steps to automate that require some judgement based on context (e.g. generating code based on docs, explaining an error given a stack trace, etc) - Steps and recipes are implemented with specific models @@ -20,4 +20,4 @@ An **LLM** is short for Large Language Model, which includes models like GPT-4, ### `gpt-turbo-3.5` -### `StarCoder` \ No newline at end of file +### `StarCoder` diff --git a/docs/docs/concepts/policy.md b/docs/docs/concepts/policy.md index 79dbca56..ea515673 100644 --- a/docs/docs/concepts/policy.md +++ b/docs/docs/concepts/policy.md @@ -8,7 +8,7 @@ A **policy** is decides what step to run next and is associated with a [autopilo ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +A relic of my original plan that ended up being the place to define slash commands, the command run on startup, and other weird stuff that you might want to inject after certain other steps. This may be the place where "hooks" turn out to be implemented. Much of this may be configurable through `continue.json/yaml` config file (this is where steps that run on GUI opening are currently configured.). Simply takes the history and returns a single step to run next. Can return None if no step to take next. Then user input will kick it off again eventually. Autopilot has a single policy that it follows, so definitely a global/user-configurable type of thing. - The Policy is where slash commands are defined -- The Policy is a global thing, so probably something we'll want to make user-configurable if we don't significantly change it \ No newline at end of file +- The Policy is a global thing, so probably something we'll want to make user-configurable if we don't significantly change it diff --git a/docs/docs/concepts/recipe.md b/docs/docs/concepts/recipe.md index 05d5eb0b..da2b6264 100644 --- a/docs/docs/concepts/recipe.md +++ b/docs/docs/concepts/recipe.md @@ -8,8 +8,8 @@ A **recipe** is an ordered sequence of [steps](./step.md) that are intended to a ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +When enough steps are strung together they become a recipe. Can kick off with slash command, can share/download somehow. - Although technically just a step itself, since they also subclass the Step class, recipes differentiate themselves from normal steps by ending their name with `Recipe` by - Technically, everything is a step since everything subclasses the step class. Steps can be composed together. Once steps are composed into a workflow that developers use and share with others, that step is called a recipe and, by convention, it ends with Recipe to signal this -- Actually just a step that is composed of only other steps / recipes. \ No newline at end of file +- Actually just a step that is composed of only other steps / recipes. diff --git a/docs/docs/concepts/sdk.md b/docs/docs/concepts/sdk.md index aab97f5f..30da2e79 100644 --- a/docs/docs/concepts/sdk.md +++ b/docs/docs/concepts/sdk.md @@ -8,8 +8,6 @@ The **Continue SDK** gives you all the tools you need to automate software devel ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** - - The ContinueSDK has a `run_step` method, which allows Steps to be composable - The reason you want to run it with `run_step` instead of creating a Step and calling `step.run(...)` is so Continue can automatically keep track of the order of all steps run, and allow for reversibility, etc... - The ContinueSDK also contains functions for very common steps, like `edit_file`, `add_file`, `run` (to run shell commands), and a few others @@ -21,7 +19,7 @@ The **Continue SDK** gives you all the tools you need to automate software devel ### `sdk.ide` -`sdk.ide` is an instance of the class `AbstractIdeProtocolServer`, which contains all the methods you might need to interact with the IDE. This includes things like reading, editing, saving, and opening files as well as getting the workspace directory, displaying suggestions, and more. The goal is to have an IDE agnostic way of interacting with IDEs, so that Steps are portable across VSCode, Sublime, Code, or any other editor you use. +`sdk.ide` is an instance of the class `AbstractIdeProtocolServer`, which contains all the methods you might need to interact with the IDE. This includes things like reading, editing, saving, and opening files as well as getting the workspace directory, displaying suggestions, and more. The goal is to have an IDE agnostic way of interacting with IDEs, so that Steps are portable across VS Code, Sublime, Code, or any other editor you use. ### `sdk.models` @@ -61,4 +59,4 @@ The below methods are all just shorthand for very common steps. ### `get_user_secret` -`get_user_secret` will retrieve a secret from the local .env file or, if it doesn't exist, prompt the user to enter the secret, then store this in the .env file. \ No newline at end of file +`get_user_secret` will retrieve a secret from the local .env file or, if it doesn't exist, prompt the user to enter the secret before moving on. diff --git a/docs/docs/concepts/step.md b/docs/docs/concepts/step.md index 61761344..b92e6faf 100644 --- a/docs/docs/concepts/step.md +++ b/docs/docs/concepts/step.md @@ -8,8 +8,6 @@ A **step** is a simple action that the LLM should take as part of a sequence tha ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** - - A `Step` is a Pydantic class inheriting from `Step` - Steps implement the `run` method, which takes a ContinueSDK as its only parameter - The ContinueSDK gives all the utilities you need to easily write recipes (Steps) @@ -60,7 +58,7 @@ Create and run an alembic migration #### Parameters -- `edited_file`: +- `edited_file`: ### WritePytestsStep @@ -68,4 +66,4 @@ Write unit tests for this file. #### Parameters -- for_filepath (required): the path of the file that unit tests should be created for \ No newline at end of file +- for_filepath (required): the path of the file that unit tests should be created for diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index bec9961c..39becd1a 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -2,15 +2,15 @@ ## GitHub Codespaces Demo -**TODO: Add `Open in GitHub Codespaces` badge here** +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/continuedev/continue-codespaces-demo?quickstart=1) 1. Click the `Open in GitHub Codespaces` badge above :::tip - We don't want to waste your time with install and env setup before you try Continue, so we set up a GitHub Codespaces dev container for you, which **won’t cost you anything**. If you are using GitHub Free for personal accounts, you can [use Codespaces for 120 hours per month for free](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces#monthly-included-storage-and-core-hours-for-personal-accounts) +We don't want to waste your time with install and env setup before you try Continue, so we set up a GitHub Codespace for you, which **won’t cost you anything**. If you are using GitHub Free for personal accounts, you can [use Codespaces for 120 hours per month for free](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces#monthly-included-storage-and-core-hours-for-personal-accounts) ::: -2. Select the `Create new codespace` button and wait a couple minutes for it to launch and then install the Continue extension. It should look like this one it is complete: +2. Select the `Create new codespace` button and wait a couple minutes while it launches and installs the Continue extension. Once complete, it should look like this: **TODO: Insert an image of Continue when it has opened** @@ -19,9 +19,9 @@ **TODO: Design and set up Pandas stuff scenario in codespaces** 4. There are a few recipes you should also try -a. In the first directory, try out X recipe -b. In the second directory, try out Y recipe -c. In the third directory, try out Z recipe + a. In the first directory, try out X recipe + b. In the second directory, try out Y recipe + c. In the third directory, try out Z recipe - database migrations - something super simple (libaries) @@ -31,10 +31,13 @@ c. In the third directory, try out Z recipe ## Next Steps -If you would prefer to use Continue locally, we reccommend installing `Continue` packaged as a VS Code extension as described [here](./install.md). +If you're ready to use Continue locally, install `Continue` packaged as a VS Code extension, as described [here](./install.md). -Otherwise, if you would like to continue to use Continue on GitHub Codespaces, then you should now go through the walkthroughs: -- How to [use the GUI](./walkthroughs/use-the-gui.md) -- How to [use a recipe](./walkthroughs/use-a-recipe.md) -- How to [create a recipe](./walkthroughs/create-a-recipe.md) -- How to [share a recipe](./walkthroughs/share-a-recipe.md) \ No newline at end of file +If you'd like to continue exploring in GitHub Codespaces, you can learn more with our walkthroughs: + +How to... + +- [Use the GUI](./walkthroughs/use-the-gui.md) +- [Invoke a recipe](./walkthroughs/use-a-recipe.md) +- [Create a recipe](./walkthroughs/create-a-recipe.md) +- [Share a recipe](./walkthroughs/share-a-recipe.md) diff --git a/docs/docs/how-continue-works.md b/docs/docs/how-continue-works.md index e032c7a4..32bf6347 100644 --- a/docs/docs/how-continue-works.md +++ b/docs/docs/how-continue-works.md @@ -2,17 +2,15 @@ ![Continue Architecture Diagram](/img/continue-architecture.png) -**TODO: Rename notebook protocol in this diagram** - ## Overview -The `Continue` library consists of a [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. +The `Continue` library consists of an [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. -The [SDK](./concepts/sdk.md) gives you access to tools (e.g. open a directory, edit a file, call a model, etc), which you can use when defining how a step should work and composing them with other steps. +The [SDK](./concepts/sdk.md) gives you access to the tools (e.g. open a directory, edit a file, call a model, etc.) needed to define steps that integrate LLMs into your IDE. -The [GUI](./concepts/gui.md) enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly. +The [GUI](./concepts/gui.md) lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. -The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS Code, a web browser, etc), enabling the steps to make changes to your code and accelerate your software development workflows. +The [Core](./concepts/core.md) holds the main event loop, responsible for connecting IDE, SDK, and GUI and deciding which steps to take next. ## Details @@ -20,9 +18,9 @@ The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS - Continue connects any code editor (primarily VS Code right now) to a server (the Continue server) that can take actions in the editor in accordance with defined recipes at the request of a user through the GUI - What this looks like: - - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. - - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the NotebookProtocol. - - Communication between the extension and GUI happens through the Continue server. + - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. + - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the NotebookProtocol. + - Communication between the extension and GUI happens through the Continue server. - When you type a natural language command in the GUI, this is sent to the Continue server, where the `Autopilot` class takes action, potentially using the ContinueIdeProtocol to request actions be taken in the IDE, and then updates the GUI to display the new history. - `core` directory contains major concepts - This includes Autopilot, Policy, SDK (all in their own files so far) @@ -36,4 +34,4 @@ The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS - `models` contains all the Pydantic models and `generate_json_schema.py`, a script that converts them to JSONSchema .json files in `schema/json` - `server` runs the servers that communicate with a) the React app (`notebook.py`) and b) the IDE (`ide.py`) - `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `notebook.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app -- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. \ No newline at end of file +- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. diff --git a/docs/docs/install.md b/docs/docs/install.md index b9916b73..6dce5da4 100644 --- a/docs/docs/install.md +++ b/docs/docs/install.md @@ -20,8 +20,11 @@ Please follow the [README instructions in the repo](https://github.com/continued ## Next steps -Now that you have installed the VS Code extension, you should go through the walkthroughs: -- How to [use the GUI](./walkthroughs/use-the-gui.md) -- How to [use a recipe](./walkthroughs/use-a-recipe.md) -- How to [create a recipe](./walkthroughs/create-a-recipe.md) -- How to [share a recipe](./walkthroughs/share-a-recipe.md) \ No newline at end of file +Now that you have installed the VS Code extension, you can learn more with our walkthroughs: + +How to... + +- [Use the GUI](./walkthroughs/use-the-gui.md) +- [Invoke a recipe](./walkthroughs/use-a-recipe.md) +- [Create a recipe](./walkthroughs/create-a-recipe.md) +- [Share a recipe](./walkthroughs/share-a-recipe.md) diff --git a/docs/docs/intro.md b/docs/docs/intro.md index e9f1dffe..5d73a256 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -2,21 +2,19 @@ ![continue-cover-logo](/img/continue-cover-logo.png) -**TODO: Nate's review of this page** +## Quickstart + +1. Try out `Continue` in the [GitHub Codespaces Demo](./getting-started.md) +2. Install `Continue` packaged as a [VS Code extension](./install.md) ## What is `Continue`? -**`Continue` is the open-source library for accelerating your use of LLMs while coding.** +**`Continue` is the open-source library for accelerating software development with language models** -You define the scenarios where Large Language Models ([LLMs](./concepts/llm.md)) like GPT-4 and StarCoder should act as an autopilot that helps you complete software development tasks. You use [recipes](./concepts/recipe.md) created by others to automate more steps in your development workflows. If a [recipe](./concepts/recipe.md) does not exist or work exactly like you want, you can use the [Continue SDK](./concepts/sdk.md) to create custom [steps](./concepts/step.md) and compose them into personalized [recipes](./concepts/recipe.md). Whether you are using a [recipe](./concepts/recipe.md) created by yourself or someone else, you can also review, reverse, and rerun [steps](./concepts/step.md) with the [Continue GUI](./concepts/gui.md), which helps you guide the work done by LLMs and learn when to use and trust them. +You define the scenarios where Large Language Models ([LLMs](./concepts/llm.md)) like GPT-4 and StarCoder should act as an autopilot, helping you complete software development tasks. You use [recipes](./concepts/recipe.md) created by others to automate more steps in your workflows. If a [recipe](./concepts/recipe.md) does not exist or work exactly like you want, you can use the [Continue SDK](./concepts/sdk.md) to create custom [steps](./concepts/step.md) and compose them into personalized [recipes](./concepts/recipe.md). Whether you are using a [recipe](./concepts/recipe.md) created by yourself or someone else, you can review, reverse, and rerun [steps](./concepts/step.md) with the [Continue GUI](./concepts/gui.md), which helps you guide the work done by LLMs and learn when to use and trust them. ## Why do developers use `Continue`? -Many developers have begun to use models like [GPT-4](https://openai.com/research/gpt-4) through [ChatGPT](https://openai.com/blog/chatgpt) while coding; however, this is quite a painful experience because of how much manual copy, paste, and editing is required to construct context for LLMs and then incorporate the generations from LLMs. Many other developers prefer to use open source models or work at organizations where they are unable to use ChatGPT, so they are using [StarCoder](https://huggingface.co/blog/starcoder) [Chat](https://huggingface.co/chat/) and are running into the same issues. - -`Continue` eliminates the manual copy, paste, and editing required when using LLMs while coding. This accelerates how developers build, ship, and maintain software, while giving them the control to define when LLMs should take actions and the confidence to trust LLMs. In short, it enables developers to do what they have always done: work together to create better and better abstractions that make it easier and easier to automate the repetitive work that people want computers to do. +Many developers have begun to use models like [GPT-4](https://openai.com/research/gpt-4) through [ChatGPT](https://openai.com/blog/chatgpt) while coding; however, the experience is painful because of how much manual copying, pasting, and editing is required to supply them with context and transfer the generated solutions to your codebase. `Continue` eliminates this pain by deeply integrating LLMs into your IDE. -## Getting started - -1. Try out `Continue` in the [GitHub Codespaces Demo](./getting-started.md) -2. Install `Continue` packaged as a [VS Code extension](./install.md) \ No newline at end of file +`Continue` accelerates how developers build, ship, and maintain software, while giving them the control to define when LLMs should take actions and the confidence to trust LLMs. In short, it enables developers to do what they have always done: work together to create better and better abstractions that make it easier and easier to automate the repetitive work that people want computers to do. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 7ca00f8d..c2991841 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -1,49 +1,49 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/dracula'); +const lightCodeTheme = require("prism-react-renderer/themes/github"); +const darkCodeTheme = require("prism-react-renderer/themes/dracula"); /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'Continue', - tagline: 'the open-source library for accelerating your use of LLMs while coding', - favicon: 'img/favicon.ico', + title: "Continue", + tagline: + "the open-source library for accelerating software development with language models", + favicon: "img/favicon.ico", // Set the production url of your site here - url: 'https://continue.dev', + url: "https://continue.dev", // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/', + baseUrl: "/", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. - organizationName: 'continuedev', // Usually your GitHub org/user name. - projectName: 'continue', // Usually your repo name. + organizationName: "continuedev", // Usually your GitHub org/user name. + projectName: "continue", // Usually your repo name. - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { - defaultLocale: 'en', - locales: ['en'], + defaultLocale: "en", + locales: ["en"], }, presets: [ [ - 'classic', + "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - sidebarPath: require.resolve('./sidebars.js'), - editUrl: - 'https://github.com/continuedev/continue/', + sidebarPath: require.resolve("./sidebars.js"), + editUrl: "https://github.com/continuedev/continue/", }, theme: { - customCss: require.resolve('./src/css/custom.css'), + customCss: require.resolve("./src/css/custom.css"), }, }), ], @@ -53,54 +53,54 @@ const config = { /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ // Replace with your project's social card - image: 'img/docusaurus-social-card.jpg', + image: "img/docusaurus-social-card.jpg", navbar: { - title: 'Continue', + title: "Continue", logo: { - alt: 'My Site Logo', - src: 'img/logo.svg', + alt: "My Site Logo", + src: "img/logo.svg", }, items: [ { - type: 'docSidebar', - sidebarId: 'docsSidebar', - position: 'left', - label: 'Docs', + type: "docSidebar", + sidebarId: "docsSidebar", + position: "left", + label: "Docs", }, { - href: 'https://github.com/continuedev/continue', - label: 'GitHub', - position: 'right', + href: "https://github.com/continuedev/continue", + label: "GitHub", + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Docs', + title: "Docs", items: [ { - label: 'Introduction', - to: '/docs/intro', + label: "Introduction", + to: "/docs/intro", }, ], }, { - title: 'Community', + title: "Community", items: [ { - label: 'Twitter', - href: 'https://twitter.com/continuedev', + label: "Twitter", + href: "https://twitter.com/continuedev", }, ], }, { - title: 'More', + title: "More", items: [ { - label: 'GitHub', - href: 'https://github.com/continuedev/continue', + label: "GitHub", + href: "https://github.com/continuedev/continue", }, ], }, @@ -114,4 +114,4 @@ const config = { }), }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/docs/src/components/HomepageFeatures/index.js b/docs/src/components/HomepageFeatures/index.js index 764ca891..0c5d8272 100644 --- a/docs/src/components/HomepageFeatures/index.js +++ b/docs/src/components/HomepageFeatures/index.js @@ -1,43 +1,44 @@ -import React from 'react'; -import clsx from 'clsx'; -import styles from './styles.module.css'; +import React from "react"; +import clsx from "clsx"; +import styles from "./styles.module.css"; const FeatureList = [ { - title: 'Define the scenarios where LLMs should step into accelerate you', - Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, + title: "Tell LLMs when to step in", + Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default, description: ( <> - Enable LLMs to be an autopilot for parts of your software development tasks - by leveraging recipes created by others in the workflows you use when coding + Seamlessly put your repetitive software development tasks on autopilot + by leveraging recipes created by others ), }, { - title: 'Create your own workflows to show LLMs exactly what to do', - Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, + title: "Write your own recipes", + Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default, description: ( <> - Use the Continue SDK to create your own custom steps and compose them into - personalized recipes, so that using LLMs seamlessly fits into your workflows + Use the Continue SDK to create your own custom steps and compose them + into personalized recipes, guiding LLMs through common sequences of + tasks ), }, { - title: 'Guide the steps taken by LLMs to learn when to use and trust them', - Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, + title: "Wield LLMs with confidence", + Svg: require("@site/static/img/undraw_docusaurus_react.svg").default, description: ( <> - Use the Continue GUI to review, reverse, and rerun steps or even entire recipes, - incorporating LLMs with confidence into your software development workflows + Use the Continue GUI to review, reverse, and rerun steps or even entire + recipes, allowing you to build trust in LLMs ), }, ]; -function Feature({Svg, title, description}) { +function Feature({ Svg, title, description }) { return ( -
+
diff --git a/docs/static/img/continue-architecture.png b/docs/static/img/continue-architecture.png index 7a38c904..58366d8f 100644 Binary files a/docs/static/img/continue-architecture.png and b/docs/static/img/continue-architecture.png differ diff --git a/extension/README.md b/extension/README.md index 0a003145..df158440 100644 --- a/extension/README.md +++ b/extension/README.md @@ -29,7 +29,7 @@ Once Continue has code snippets to work with, it can generate a fix. Just click ### Stacktrace Parsing -Any stacktrace that appears in your VSCode terminal will be caught by us so we can immediately begin the debugging process. For small bugs that you might have quickly solved, we'll just speed up the process to be nearly instantaneous. +Any stacktrace that appears in your VS Code terminal will be caught by us so we can immediately begin the debugging process. For small bugs that you might have quickly solved, we'll just speed up the process to be nearly instantaneous. ### Generate Unit Tests and Docstrings diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx index 626ae42f..c0ab9400 100644 --- a/extension/react-app/src/components/CodeMultiselect.tsx +++ b/extension/react-app/src/components/CodeMultiselect.tsx @@ -135,7 +135,7 @@ function formatFileRange( )} (lines ${rangeInFile.range.start.line + 1}-${ rangeInFile.range.end.line + 1 })`; - // +1 because VSCode Ranges are 0-indexed + // +1 because VS Code Ranges are 0-indexed } //#endregion diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx index 6219654d..12ce5af8 100644 --- a/extension/react-app/src/components/VSCodeFileLink.tsx +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -1,7 +1,7 @@ import React from "react"; import { postVscMessage } from "../vscode"; -function VSCodeFileLink(props: { path: string; text?: string }) { +function VS CodeFileLink(props: { path: string; text?: string }) { return ( requirements.txt` whenever you add a new requirement. diff --git a/extension/src/README.md b/extension/src/README.md index bb10f5c8..76b96ea0 100644 --- a/extension/src/README.md +++ b/extension/src/README.md @@ -22,23 +22,23 @@ 10. Then run `npm run compile` -7. Open `src/activate.ts` file (or any TypeScript file) +11. Open `src/activate.ts` file (or any TypeScript file) -7. Press `F5` on your keyboard to start `Run and Debug` mode +12. Press `F5` on your keyboard to start `Run and Debug` mode -8. `cmd+shift+p` to look at developer console and select Continue commands +13. `cmd+shift+p` to look at developer console and select Continue commands -9. Every time you make changes to the code, you need to run `npm run compile` +14. Every time you make changes to the code, you need to run `npm run compile` -10. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` +15. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` ## Alternative: Install a packaged version -You should always have a packaged version installed in VSCode, because when Continue is broken you'll want a stable version to help you debug. There are four key commands in the `package.json`: +You should always have a packaged version installed in VS Code, because when Continue is broken you'll want a stable version to help you debug. There are four key commands in the `package.json`: 1. `npm run package` will create a .vsix file in the `build/` folder that can then be installed. It is this same file that you can share with others who want to try the extension. -2. `npm run install-extension` will install the extension to VSCode. You should then see it in your installed extensions in the VSCode sidebar. +2. `npm run install-extension` will install the extension to VS Code. You should then see it in your installed extensions in the VS Code sidebar. 3. `npm run uninstall` will uninstall the extension. You don't always have to do this thanks to the reinstall command, but can be useful when you want to do so manually. -- cgit v1.2.3-70-g09d2 From ad86eff7b06f0bfbed3b1cb362d83ec6a4348e45 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 01:05:59 -0400 Subject: notebook -> gui --- continuedev/src/continuedev/core/agent.py | 186 --------------- continuedev/src/continuedev/core/autopilot.py | 186 +++++++++++++++ continuedev/src/continuedev/plugins/__init__.py | 26 -- continuedev/src/continuedev/plugins/load.py | 21 -- .../src/continuedev/plugins/policy/__init__.py | 4 - .../src/continuedev/plugins/policy/hookspecs.py | 10 - .../continuedev/plugins/policy/libs/__init__.py | 0 .../continuedev/plugins/policy/libs/alternate.py | 20 -- .../src/continuedev/plugins/step/__init__.py | 4 - .../src/continuedev/plugins/step/hookspecs.py | 15 -- .../src/continuedev/plugins/step/libs/__init__.py | 0 .../continuedev/plugins/step/libs/hello_world.py | 9 - continuedev/src/continuedev/server/gui.py | 130 ++++++++++ continuedev/src/continuedev/server/gui_protocol.py | 28 +++ continuedev/src/continuedev/server/ide.py | 16 +- continuedev/src/continuedev/server/ide_protocol.py | 12 +- continuedev/src/continuedev/server/main.py | 4 +- continuedev/src/continuedev/server/notebook.py | 130 ---------- .../src/continuedev/server/notebook_protocol.py | 28 --- docs/docs/concepts/agent.md | 8 - docs/docs/concepts/autopilot.md | 30 +++ docs/docs/concepts/gui.md | 4 +- docs/docs/concepts/ide.md | 14 +- docs/docs/how-continue-works.md | 37 +++ extension/react-app/src/App.tsx | 6 +- .../src/hooks/ContinueGUIClientProtocol.ts | 13 + .../src/hooks/ContinueNotebookClientProtocol.ts | 13 - .../react-app/src/hooks/useContinueGUIProtocol.ts | 49 ++++ .../src/hooks/useContinueNotebookProtocol.ts | 49 ---- extension/react-app/src/hooks/useWebsocket.ts | 16 +- extension/react-app/src/tabs/gui.tsx | 265 +++++++++++++++++++++ extension/react-app/src/tabs/notebook.tsx | 265 --------------------- extension/src/activation/activate.ts | 4 +- extension/src/commands.ts | 4 +- extension/src/continueIdeClient.ts | 8 +- 35 files changed, 782 insertions(+), 832 deletions(-) delete mode 100644 continuedev/src/continuedev/core/agent.py create mode 100644 continuedev/src/continuedev/core/autopilot.py delete mode 100644 continuedev/src/continuedev/plugins/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/load.py delete mode 100644 continuedev/src/continuedev/plugins/policy/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/alternate.py delete mode 100644 continuedev/src/continuedev/plugins/step/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/hello_world.py create mode 100644 continuedev/src/continuedev/server/gui.py create mode 100644 continuedev/src/continuedev/server/gui_protocol.py delete mode 100644 continuedev/src/continuedev/server/notebook.py delete mode 100644 continuedev/src/continuedev/server/notebook_protocol.py delete mode 100644 docs/docs/concepts/agent.md create mode 100644 docs/docs/concepts/autopilot.md create mode 100644 docs/docs/how-continue-works.md create mode 100644 extension/react-app/src/hooks/ContinueGUIClientProtocol.ts delete mode 100644 extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts create mode 100644 extension/react-app/src/hooks/useContinueGUIProtocol.ts delete mode 100644 extension/react-app/src/hooks/useContinueNotebookProtocol.ts create mode 100644 extension/react-app/src/tabs/gui.tsx delete mode 100644 extension/react-app/src/tabs/notebook.tsx (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py deleted file mode 100644 index 6e920ab4..00000000 --- a/continuedev/src/continuedev/core/agent.py +++ /dev/null @@ -1,186 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from ..libs.util.telemetry import capture_event -from .sdk import ContinueSDK -import asyncio - - -class Autopilot(ContinueBaseModel): - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Coroutine["FullState", None, None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - async def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - await callback(full_state) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(str(index), input) - - async def wait_for_user_input(self) -> str: - self._active = False - await self.update_subscribers() - user_input = await self._user_input_queue.get(str(self.history.current_index)) - self._active = True - await self.update_subscribers() - return user_input - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(ContinueSDK(self)) - - await self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - capture_event( - 'step run', {'step_name': step.name, 'params': step.dict()}) - - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Call all subscribed callbacks - await self.update_subscribers() - - # Run step - self._step_depth += 1 - observation = await step(ContinueSDK(self)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_last_at_depth( - self._step_depth, include_current=True).observation = observation - - # Update its description - async def update_description(): - step._set_description(await step.describe(ContinueSDK(self).models)) - # Update subscribers with new description - await self.update_subscribers() - asyncio.create_task(update_description()) - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Autopilot is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - await callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - await self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - await self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py new file mode 100644 index 00000000..6e920ab4 --- /dev/null +++ b/continuedev/src/continuedev/core/autopilot.py @@ -0,0 +1,186 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .main import Policy, History, FullState, Step, HistoryNode +from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from ..libs.util.telemetry import capture_event +from .sdk import ContinueSDK +import asyncio + + +class Autopilot(ContinueBaseModel): + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + _on_update_callbacks: List[Callable[[FullState], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Coroutine["FullState", None, None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + async def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + await callback(full_state) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(str(index), input) + + async def wait_for_user_input(self) -> str: + self._active = False + await self.update_subscribers() + user_input = await self._user_input_queue.get(str(self.history.current_index)) + self._active = True + await self.update_subscribers() + return user_input + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(ContinueSDK(self)) + + await self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + capture_event( + 'step run', {'step_name': step.name, 'params': step.dict()}) + + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Call all subscribed callbacks + await self.update_subscribers() + + # Run step + self._step_depth += 1 + observation = await step(ContinueSDK(self)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_last_at_depth( + self._step_depth, include_current=True).observation = observation + + # Update its description + async def update_description(): + step._set_description(await step.describe(ContinueSDK(self).models)) + # Update subscribers with new description + await self.update_subscribers() + asyncio.create_task(update_description()) + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Autopilot is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + await callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + await self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + await self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/plugins/__init__.py b/continuedev/src/continuedev/plugins/__init__.py deleted file mode 100644 index 0ce6d079..00000000 --- a/continuedev/src/continuedev/plugins/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import List -import pluggy -from .step import hookspecs -from .step.libs import hello_world - -builtin_libs = [hello_world] - -def get_plugin_manager(use_plugins: List[str]) -> pluggy.PluginManager: - pm = pluggy.PluginManager("continue.step") - pm.add_hookspecs(hookspecs) - pm.load_setuptools_entrypoints("continue.step") - - # Only use plugins that are specified in the config file - for plugin, name in pm.list_name_plugin(): - if name not in use_plugins: - pm.set_blocked(plugin) - - # Print warning if plugin not found - for name in use_plugins: - if not pm.has_plugin(name): - print(f"Plugin {name} not found.") - - for lib in builtin_libs: - pm.register(lib) - - return pm \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/load.py b/continuedev/src/continuedev/plugins/load.py deleted file mode 100644 index adbaad09..00000000 --- a/continuedev/src/continuedev/plugins/load.py +++ /dev/null @@ -1,21 +0,0 @@ -def load_validator_plugin(config: ValidatorPluginConfig) -> Validator: - if config.name == "continue.tb_validator": - return PythonTracebackValidator(config.cmd, config.cwd) - elif config.name == "continue.pytest_validator": - return PytestValidator(cwd=config.cwd) - else: - raise KeyError("Unknown validator plugin name") - -def load_llm_plugin(config: LLMPluginConfig) -> LLM: - if config.provider == "openai": - return OpenAI(api_key=config.api_key) - else: - raise KeyError("Unknown LLM provider: " + config.provider) - -def load_policy_plugin(config: PolicyPluginConfig) -> Policy: - if config.name == "continue.random_policy": - return RandomPolicy() - elif config.name == "continue.dfs_policy": - return DFSPolicy() - else: - raise KeyError("Unknown policy plugin name") \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/__init__.py b/continuedev/src/continuedev/plugins/policy/__init__.py deleted file mode 100644 index b9722bae..00000000 --- a/continuedev/src/continuedev/plugins/policy/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.policy") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/hookspecs.py b/continuedev/src/continuedev/plugins/policy/hookspecs.py deleted file mode 100644 index abe932d3..00000000 --- a/continuedev/src/continuedev/plugins/policy/hookspecs.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Tuple -import pluggy -from ...libs.policy import Policy, Step - -hookspec = pluggy.HookspecMarker("continue.policy") - -class PolicyPlugin(Policy): - @hookspec - def next(self) -> Step: - """Get the next step to run""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/libs/__init__.py b/continuedev/src/continuedev/plugins/policy/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py deleted file mode 100644 index 3087c059..00000000 --- a/continuedev/src/continuedev/plugins/policy/libs/alternate.py +++ /dev/null @@ -1,20 +0,0 @@ -from plugins import policy -from ....core.main import History, Step - - -class AlternatingPolicy: - """A Policy that alternates between two steps.""" - - def __init__(self, first: Step, second: Step): - self.first = first - self.second = second - self.last_was_first = False - - @policy.hookimpl - def next(self, history: History) -> Step: - if self.last_was_first: - self.last_was_first = False - return self.second - else: - self.last_was_first = True - return self.first diff --git a/continuedev/src/continuedev/plugins/step/__init__.py b/continuedev/src/continuedev/plugins/step/__init__.py deleted file mode 100644 index e6d8cd3b..00000000 --- a/continuedev/src/continuedev/plugins/step/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.step") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py deleted file mode 100644 index a5714fc5..00000000 --- a/continuedev/src/continuedev/plugins/step/hookspecs.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Coroutine -import pluggy -from ...core.main import Step -from ...core.observation import Observation -from ...core.sdk import ContinueSDK - -hookspec = pluggy.HookspecMarker("continue.step") - -# Perhaps Actions should be generic about what their inputs must be. - - -class StepPlugin(Step): - @hookspec - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - """Run""" diff --git a/continuedev/src/continuedev/plugins/step/libs/__init__.py b/continuedev/src/continuedev/plugins/step/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/step/libs/hello_world.py b/continuedev/src/continuedev/plugins/step/libs/hello_world.py deleted file mode 100644 index 72255bfd..00000000 --- a/continuedev/src/continuedev/plugins/step/libs/hello_world.py +++ /dev/null @@ -1,9 +0,0 @@ -from ....plugins import step -from ....libs.steps import ContinueSDK - - -class HelloWorldStep: - """A Step that prints "Hello World!".""" - @step.hookimpl - def run(sdk: ContinueSDK): - print("Hello World!") diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py new file mode 100644 index 00000000..3d1a5a82 --- /dev/null +++ b/continuedev/src/continuedev/server/gui.py @@ -0,0 +1,130 @@ +import json +from fastapi import Depends, Header, WebSocket, APIRouter +from typing import Any, Type, TypeVar, Union +from pydantic import BaseModel +from uvicorn.main import Server + +from .session_manager import SessionManager, session_manager, Session +from .gui_protocol import AbstractGUIProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +import asyncio +import nest_asyncio +nest_asyncio.apply() + +router = APIRouter(prefix="/gui", tags=["gui"]) + +# Graceful shutdown by closing websockets +original_handler = Server.handle_exit + + +class AppStatus: + should_exit = False + + @staticmethod + def handle_exit(*args, **kwargs): + AppStatus.should_exit = True + print("Shutting down") + original_handler(*args, **kwargs) + + +Server.handle_exit = AppStatus.handle_exit + + +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) + + +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) + + +T = TypeVar("T", bound=BaseModel) + +# You should probably abstract away the websocket stuff into a separate class + + +class GUIProtocolServer(AbstractGUIProtocolServer): + websocket: WebSocket + session: Session + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + + def __init__(self, session: Session): + self.session = session + + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "messageType": message_type, + "data": data + }) + + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(message_type) + + async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: + await self._send_json(message_type, data) + resp = await self._receive_json(message_type) + return resp_model.parse_obj(resp) + + def handle_json(self, message_type: str, data: Any): + try: + if message_type == "main_input": + self.on_main_input(data["input"]) + elif message_type == "step_user_input": + self.on_step_user_input(data["input"], data["index"]) + elif message_type == "refinement_input": + self.on_refinement_input(data["input"], data["index"]) + elif message_type == "reverse_to_index": + self.on_reverse_to_index(data["index"]) + except Exception as e: + print(e) + + async def send_state_update(self): + state = self.session.autopilot.get_full_state().dict() + await self._send_json("state_update", { + "state": state + }) + + def on_main_input(self, input: str): + # Do something with user input + asyncio.create_task(self.session.autopilot.accept_user_input(input)) + + def on_reverse_to_index(self, index: int): + # Reverse the history to the given index + asyncio.create_task(self.session.autopilot.reverse_to_index(index)) + + def on_step_user_input(self, input: str, index: int): + asyncio.create_task( + self.session.autopilot.give_user_input(input, index)) + + def on_refinement_input(self, input: str, index: int): + asyncio.create_task( + self.session.autopilot.accept_refinement_input(input, index)) + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): + await websocket.accept() + + print("Session started") + session_manager.register_websocket(session.session_id, websocket) + protocol = GUIProtocolServer(session) + protocol.websocket = websocket + + # Update any history that may have happened before connection + await protocol.send_state_update() + + while AppStatus.should_exit is False: + message = await websocket.receive_text() + print("Received message", message) + if type(message) is str: + message = json.loads(message) + + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + + protocol.handle_json(message_type, data) + + print("Closing websocket") + await websocket.close() diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py new file mode 100644 index 00000000..e32d80ef --- /dev/null +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -0,0 +1,28 @@ +from typing import Any +from abc import ABC, abstractmethod + + +class AbstractGUIProtocolServer(ABC): + @abstractmethod + async def handle_json(self, data: Any): + """Handle a json message""" + + @abstractmethod + def on_main_input(self, input: str): + """Called when the user inputs something""" + + @abstractmethod + def on_reverse_to_index(self, index: int): + """Called when the user requests reverse to a previous index""" + + @abstractmethod + def on_refinement_input(self, input: str, index: int): + """Called when the user inputs a refinement""" + + @abstractmethod + def on_step_user_input(self, input: str, index: int): + """Called when the user inputs a step""" + + @abstractmethod + async def send_state_update(self, state: dict): + """Send a state update to the client""" diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 32f0b3ba..71017ce0 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -12,7 +12,7 @@ from ..models.filesystem import FileSystem, RangeInFile, EditDiff, RealFileSyste from ..models.main import Traceback from ..models.filesystem_edit import AddDirectory, AddFile, DeleteDirectory, DeleteFile, FileSystemEdit, FileEdit, FileEditWithFullContents, RenameDirectory, RenameFile, SequentialFileSystemEdit from pydantic import BaseModel -from .notebook import SessionManager, session_manager +from .gui import SessionManager, session_manager from .ide_protocol import AbstractIdeProtocolServer @@ -106,8 +106,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): return resp_model.parse_obj(resp) async def handle_json(self, message_type: str, data: Any): - if message_type == "openNotebook": - await self.openNotebook() + if message_type == "openGUI": + await self.openGUI() elif message_type == "setFileOpen": await self.setFileOpen(data["filepath"], data["open"]) elif message_type == "fileEdits": @@ -131,9 +131,9 @@ class IdeProtocolServer(AbstractIdeProtocolServer): "open": open }) - async def openNotebook(self): + async def openGUI(self): session_id = self.session_manager.new_session(self) - await self._send_json("openNotebook", { + await self._send_json("openGUI", { "sessionId": session_id }) @@ -148,7 +148,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self._receive_json(ShowSuggestionResponse) for i in range(len(suggestions)) ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the autopilot. - # Just need connect the suggestionId to the IDE (and the notebook) + # Just need connect the suggestionId to the IDE (and the gui) return any([r.accepted for r in responses]) # ------------------------------- # @@ -168,11 +168,11 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # Access to Autopilot (so SessionManager) pass - def onCloseNotebook(self, session_id: str): + def onCloseGUI(self, session_id: str): # Accesss to SessionManager pass - def onOpenNotebookRequest(self): + def onOpenGUIRequest(self): pass def onFileEdits(self, edits: List[FileEditWithFullContents]): diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 15d019b4..4f505e80 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -24,8 +24,8 @@ class AbstractIdeProtocolServer(ABC): """Set whether a file is open""" @abstractmethod - async def openNotebook(self): - """Open a notebook""" + async def openGUI(self): + """Open a GUI""" @abstractmethod async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: @@ -44,12 +44,12 @@ class AbstractIdeProtocolServer(ABC): """Called when a file system update is received""" @abstractmethod - def onCloseNotebook(self, session_id: str): - """Called when a notebook is closed""" + def onCloseGUI(self, session_id: str): + """Called when a GUI is closed""" @abstractmethod - def onOpenNotebookRequest(self): - """Called when a notebook is requested to be opened""" + def onOpenGUIRequest(self): + """Called when a GUI is requested to be opened""" @abstractmethod async def getOpenFiles(self) -> List[str]: diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 1ffe1450..7b7124de 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -2,14 +2,14 @@ import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .ide import router as ide_router -from .notebook import router as notebook_router +from .gui import router as gui_router import uvicorn import argparse app = FastAPI() app.include_router(ide_router) -app.include_router(notebook_router) +app.include_router(gui_router) # Add CORS support app.add_middleware( diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py deleted file mode 100644 index 8ebe2853..00000000 --- a/continuedev/src/continuedev/server/notebook.py +++ /dev/null @@ -1,130 +0,0 @@ -import json -from fastapi import Depends, Header, WebSocket, APIRouter -from typing import Any, Type, TypeVar, Union -from pydantic import BaseModel -from uvicorn.main import Server - -from .session_manager import SessionManager, session_manager, Session -from .notebook_protocol import AbstractNotebookProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -import asyncio -import nest_asyncio -nest_asyncio.apply() - -router = APIRouter(prefix="/notebook", tags=["notebook"]) - -# Graceful shutdown by closing websockets -original_handler = Server.handle_exit - - -class AppStatus: - should_exit = False - - @staticmethod - def handle_exit(*args, **kwargs): - AppStatus.should_exit = True - print("Shutting down") - original_handler(*args, **kwargs) - - -Server.handle_exit = AppStatus.handle_exit - - -def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return session_manager.get_session(x_continue_session_id) - - -def websocket_session(session_id: str) -> Session: - return session_manager.get_session(session_id) - - -T = TypeVar("T", bound=BaseModel) - -# You should probably abstract away the websocket stuff into a separate class - - -class NotebookProtocolServer(AbstractNotebookProtocolServer): - websocket: WebSocket - session: Session - sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() - - def __init__(self, session: Session): - self.session = session - - async def _send_json(self, message_type: str, data: Any): - await self.websocket.send_json({ - "messageType": message_type, - "data": data - }) - - async def _receive_json(self, message_type: str) -> Any: - return await self.sub_queue.get(message_type) - - async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: - await self._send_json(message_type, data) - resp = await self._receive_json(message_type) - return resp_model.parse_obj(resp) - - def handle_json(self, message_type: str, data: Any): - try: - if message_type == "main_input": - self.on_main_input(data["input"]) - elif message_type == "step_user_input": - self.on_step_user_input(data["input"], data["index"]) - elif message_type == "refinement_input": - self.on_refinement_input(data["input"], data["index"]) - elif message_type == "reverse_to_index": - self.on_reverse_to_index(data["index"]) - except Exception as e: - print(e) - - async def send_state_update(self): - state = self.session.autopilot.get_full_state().dict() - await self._send_json("state_update", { - "state": state - }) - - def on_main_input(self, input: str): - # Do something with user input - asyncio.create_task(self.session.autopilot.accept_user_input(input)) - - def on_reverse_to_index(self, index: int): - # Reverse the history to the given index - asyncio.create_task(self.session.autopilot.reverse_to_index(index)) - - def on_step_user_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.give_user_input(input, index)) - - def on_refinement_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.accept_refinement_input(input, index)) - - -@router.websocket("/ws") -async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): - await websocket.accept() - - print("Session started") - session_manager.register_websocket(session.session_id, websocket) - protocol = NotebookProtocolServer(session) - protocol.websocket = websocket - - # Update any history that may have happened before connection - await protocol.send_state_update() - - while AppStatus.should_exit is False: - message = await websocket.receive_text() - print("Received message", message) - if type(message) is str: - message = json.loads(message) - - if "messageType" not in message or "data" not in message: - continue - message_type = message["messageType"] - data = message["data"] - - protocol.handle_json(message_type, data) - - print("Closing websocket") - await websocket.close() diff --git a/continuedev/src/continuedev/server/notebook_protocol.py b/continuedev/src/continuedev/server/notebook_protocol.py deleted file mode 100644 index c2be82e0..00000000 --- a/continuedev/src/continuedev/server/notebook_protocol.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any -from abc import ABC, abstractmethod - - -class AbstractNotebookProtocolServer(ABC): - @abstractmethod - async def handle_json(self, data: Any): - """Handle a json message""" - - @abstractmethod - def on_main_input(self, input: str): - """Called when the user inputs something""" - - @abstractmethod - def on_reverse_to_index(self, index: int): - """Called when the user requests reverse to a previous index""" - - @abstractmethod - def on_refinement_input(self, input: str, index: int): - """Called when the user inputs a refinement""" - - @abstractmethod - def on_step_user_input(self, input: str, index: int): - """Called when the user inputs a step""" - - @abstractmethod - async def send_state_update(self, state: dict): - """Send a state update to the client""" diff --git a/docs/docs/concepts/agent.md b/docs/docs/concepts/agent.md deleted file mode 100644 index bbcc6f57..00000000 --- a/docs/docs/concepts/agent.md +++ /dev/null @@ -1,8 +0,0 @@ -# Autopilot - -`Autopilot` contains the - -- History -- LLM -- Policy -- IDE diff --git a/docs/docs/concepts/autopilot.md b/docs/docs/concepts/autopilot.md new file mode 100644 index 00000000..71090eb0 --- /dev/null +++ b/docs/docs/concepts/autopilot.md @@ -0,0 +1,30 @@ +# Autopilot + +**TODO: Better explain in one sentence what this is and what its purpose is** + +:::info +The **autopilot** is the main loop, completing steps and then deciding the next step and repeating +::: + +## Details + +The Autopilot class is the center of Continue. Every step is initiated from the Autopilot, which provides it with a ContinueSDK. + +- Records history +- Allows reversal +- Injects SDK +- Has policy to decide what step to take next +- Accepts user input and acts on it +- Main event loop +- Contains main classes that are provided through the SDK, including LLM, History, IDE + +--- + +- An autopilot takes user input from the React app +- You can see this happening in `server/gui.py` +- It basically queues user inputs, pops off the most recent, runs that as a "UserInputStep", uses its Policy to run other steps until the next step is None, and then pops off the next user input. When nothing left, just waits for more +- `Autopilot` contains the + - History + - LLM + - Policy + - IDE diff --git a/docs/docs/concepts/gui.md b/docs/docs/concepts/gui.md index dfdc2a7a..f9cff697 100644 --- a/docs/docs/concepts/gui.md +++ b/docs/docs/concepts/gui.md @@ -3,11 +3,11 @@ The `GUI` enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly. **From GUI to Core** + - Natural language instructions from the developer - Hover / clicked on a step - Other user input **From Core to GUI** -- Updates to state (e.g. a new step) -**Q: do we call this the Continue GUI or Notebook?** \ No newline at end of file +- Updates to state (e.g. a new step) diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md index 980b589d..4684c362 100644 --- a/docs/docs/concepts/ide.md +++ b/docs/docs/concepts/ide.md @@ -24,9 +24,9 @@ Get the workspace directory Set whether a file is open -### openNotebook +### openGUI -Open a notebook +Open a gui ### showSuggestionsAndWait @@ -44,13 +44,13 @@ Called when a traceback is received Called when a file system update is received -### onCloseNotebook +### onCloseGUI -Called when a notebook is closed +Called when a gui is closed -### onOpenNotebookRequest +### onOpenGUIRequest -Called when a notebook is requested to be opened +Called when a gui is requested to be opened ### getOpenFiles @@ -78,4 +78,4 @@ Apply a file edit ### saveFile -Save a file \ No newline at end of file +Save a file diff --git a/docs/docs/how-continue-works.md b/docs/docs/how-continue-works.md new file mode 100644 index 00000000..e6648cbc --- /dev/null +++ b/docs/docs/how-continue-works.md @@ -0,0 +1,37 @@ +# How `Continue` works + +![Continue Architecture Diagram](/img/continue-architecture.png) + +## Overview + +The `Continue` library consists of an [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. + +The [SDK](./concepts/sdk.md) gives you access to the tools (e.g. open a directory, edit a file, call a model, etc.) needed to define steps that integrate LLMs into your IDE. + +The [GUI](./concepts/gui.md) lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. + +The [Core](./concepts/core.md) holds the main event loop, responsible for connecting IDE, SDK, and GUI and deciding which steps to take next. + +## Details + +**TODO: Refactor all of this and make it fit with language above** + +- Continue connects any code editor (primarily VS Code right now) to a server (the Continue server) that can take actions in the editor in accordance with defined recipes at the request of a user through the GUI +- What this looks like: + - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. + - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the GUIProtocol. + - Communication between the extension and GUI happens through the Continue server. + - When you type a natural language command in the GUI, this is sent to the Continue server, where the `Autopilot` class takes action, potentially using the ContinueIdeProtocol to request actions be taken in the IDE, and then updates the GUI to display the new history. +- `core` directory contains major concepts + - This includes Autopilot, Policy, SDK (all in their own files so far) + - It also includes `main.py`, which contains History, HistoryNode, Step, and others + - You'll find `env.py` here too, which is a common place to load environment variables, which can then be imported from here +- `libs` contains misc. stuff +- `llm` for language model utilities +- `steps` for builtin Continue steps +- `util` for very misc. stuff +- `chroma` for chroma code that deals with codebase embeddings +- `models` contains all the Pydantic models and `generate_json_schema.py`, a script that converts them to JSONSchema .json files in `schema/json` +- `server` runs the servers that communicate with a) the React app (`gui.py`) and b) the IDE (`ide.py`) +- `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `gui.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app +- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx index 0c40ced1..a51541d0 100644 --- a/extension/react-app/src/App.tsx +++ b/extension/react-app/src/App.tsx @@ -4,7 +4,7 @@ import { Provider } from "react-redux"; import store from "./redux/store"; import WelcomeTab from "./tabs/welcome"; import ChatTab from "./tabs/chat"; -import Notebook from "./tabs/notebook"; +import GUI from "./tabs/gui"; function App() { return ( @@ -13,8 +13,8 @@ function App() { , - title: "Notebook", + element: , + title: "GUI", }, // { element: , title: "Debug Panel" }, // { element: , title: "Welcome" }, diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts new file mode 100644 index 00000000..18a91de7 --- /dev/null +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -0,0 +1,13 @@ +abstract class AbstractContinueGUIClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; +} + +export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts deleted file mode 100644 index 75fd7373..00000000 --- a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts +++ /dev/null @@ -1,13 +0,0 @@ -abstract class AbstractContinueNotebookClientProtocol { - abstract sendMainInput(input: string): void; - - abstract reverseToIndex(index: number): void; - - abstract sendRefinementInput(input: string, index: number): void; - - abstract sendStepUserInput(input: string, index: number): void; - - abstract onStateUpdate(state: any): void; -} - -export default AbstractContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts new file mode 100644 index 00000000..a3a1d0c9 --- /dev/null +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -0,0 +1,49 @@ +import AbstractContinueGUIClientProtocol from "./ContinueGUIClientProtocol"; +// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; + +class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; + + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + if (useVscodeMessagePassing) { + this.messenger = new VscodeMessenger(serverUrlWithSessionId); + } else { + this.messenger = new WebsocketMessenger(serverUrlWithSessionId); + } + } + + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } + + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } + + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } +} + +export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts deleted file mode 100644 index b785cc84..00000000 --- a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts +++ /dev/null @@ -1,49 +0,0 @@ -import AbstractContinueNotebookClientProtocol from "./ContinueNotebookClientProtocol"; -// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; -import { Messenger, WebsocketMessenger } from "./messenger"; -import { VscodeMessenger } from "./vscodeMessenger"; - -class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProtocol { - messenger: Messenger; - // Server URL must contain the session ID param - serverUrlWithSessionId: string; - - constructor( - serverUrlWithSessionId: string, - useVscodeMessagePassing: boolean - ) { - super(); - this.serverUrlWithSessionId = serverUrlWithSessionId; - if (useVscodeMessagePassing) { - this.messenger = new VscodeMessenger(serverUrlWithSessionId); - } else { - this.messenger = new WebsocketMessenger(serverUrlWithSessionId); - } - } - - sendMainInput(input: string) { - this.messenger.send("main_input", { input }); - } - - reverseToIndex(index: number) { - this.messenger.send("reverse_to_index", { index }); - } - - sendRefinementInput(input: string, index: number) { - this.messenger.send("refinement_input", { input, index }); - } - - sendStepUserInput(input: string, index: number) { - this.messenger.send("step_user_input", { input, index }); - } - - onStateUpdate(callback: (state: any) => void) { - this.messenger.onMessageType("state_update", (data: any) => { - if (data.state) { - callback(data.state); - } - }); - } -} - -export default ContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 016fa17d..e762666f 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,15 +1,15 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; -import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; +import ContinueGUIClientProtocol from "./useContinueGUIProtocol"; import { postVscMessage } from "../vscode"; -function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { +function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); - const [client, setClient] = useState< - ContinueNotebookClientProtocol | undefined - >(undefined); + const [client, setClient] = useState( + undefined + ); useEffect(() => { if (!sessionId || !serverHttpUrl) { @@ -22,12 +22,12 @@ function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { const serverUrlWithSessionId = serverHttpUrl.replace("http", "ws") + - "/notebook/ws?session_id=" + + "/gui/ws?session_id=" + encodeURIComponent(sessionId); console.log("Creating websocket", serverUrlWithSessionId); console.log("Using vscode message passing", useVscodeMessagePassing); - const newClient = new ContinueNotebookClientProtocol( + const newClient = new ContinueGUIClientProtocol( serverUrlWithSessionId, useVscodeMessagePassing ); @@ -36,4 +36,4 @@ function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { return client; } -export default useContinueNotebookProtocol; +export default useContinueGUIProtocol; diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx new file mode 100644 index 00000000..5ddddbfc --- /dev/null +++ b/extension/react-app/src/tabs/gui.tsx @@ -0,0 +1,265 @@ +import styled from "styled-components"; +import { + Button, + defaultBorderRadius, + vscBackground, + MainTextInput, + Loader, +} from "../components"; +import ContinueButton from "../components/ContinueButton"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { History } from "../../../schema/History"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import StepContainer from "../components/StepContainer"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; +import useContinueWebsocket from "../hooks/useWebsocket"; +import useContinueGUIProtocol from "../hooks/useWebsocket"; + +let TopGUIDiv = styled.div` + display: grid; + grid-template-columns: 1fr; +`; + +let UserInputQueueItem = styled.div` + border-radius: ${defaultBorderRadius}; + color: gray; + padding: 8px; + margin: 8px; + text-align: center; +`; + +interface GUIProps { + firstObservation?: any; +} + +function GUI(props: GUIProps) { + const [waitingForSteps, setWaitingForSteps] = useState(false); + const [userInputQueue, setUserInputQueue] = useState([]); + const [history, setHistory] = useState(); + // { + // timeline: [ + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [ + // { + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // }, + // null, + // ], + // }, + // { + // step: { + // name: "EditCodeStep", + // range_in_files: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // }, + // ], + // prompt: + // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", + // description: + // "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // }, + // output: [ + // null, + // { + // reversible: true, + // actions: [ + // { + // reversible: true, + // filesystem: {}, + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // replacement: + // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", + // }, + // ], + // }, + // ], + // }, + // { + // step: { + // name: "SolveTracebackStep", + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // description: "Running step: SolveTracebackStep", + // }, + // output: [null, null], + // }, + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [null, null], + // }, + // ], + // current_index: 0, + // } as any + // ); + + const client = useContinueGUIProtocol(); + + useEffect(() => { + console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); + client?.onStateUpdate((state) => { + console.log("Received state update: ", state); + setWaitingForSteps(state.active); + setHistory(state.history); + setUserInputQueue(state.user_input_queue); + }); + }, [client]); + + const mainTextInputRef = useRef(null); + + useEffect(() => { + if (mainTextInputRef.current) { + mainTextInputRef.current.focus(); + let handler = (event: any) => { + if (event.data.type === "focusContinueInput") { + mainTextInputRef.current?.focus(); + } + }; + window.addEventListener("message", handler); + return () => { + window.removeEventListener("message", handler); + }; + } + }, [mainTextInputRef]); + + const onMainTextInput = () => { + if (mainTextInputRef.current) { + if (!client) return; + let input = mainTextInputRef.current.value; + setWaitingForSteps(true); + client.sendMainInput(input); + setUserInputQueue((queue) => { + return [...queue, input]; + }); + mainTextInputRef.current.value = ""; + mainTextInputRef.current.style.height = ""; + } + }; + + const onStepUserInput = (input: string, index: number) => { + if (!client) return; + console.log("Sending step user input", input, index); + client.sendStepUserInput(input, index); + }; + + // const iterations = useSelector(selectIterations); + return ( + + {typeof client === "undefined" && ( + <> + +

Server disconnected

+ + )} + {history?.timeline.map((node: HistoryNode, index: number) => { + return ( + { + onStepUserInput(input, index); + }} + inFuture={index > history?.current_index} + historyNode={node} + onRefinement={(input: string) => { + client?.sendRefinementInput(input, index); + }} + onReverse={() => { + client?.reverseToIndex(index); + }} + /> + ); + })} + {waitingForSteps && } + +
+ {userInputQueue.map((input) => { + return {input}; + })} +
+ + { + if (e.key === "Enter") { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); + } + }} + rows={1} + onChange={() => { + let textarea = mainTextInputRef.current!; + textarea.style.height = ""; /* Reset the height*/ + textarea.style.height = + Math.min(textarea.scrollHeight - 15, 500) + "px"; + }} + > + +
+ ); +} + +export default GUI; diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx deleted file mode 100644 index 02c9ff31..00000000 --- a/extension/react-app/src/tabs/notebook.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import styled from "styled-components"; -import { - Button, - defaultBorderRadius, - vscBackground, - MainTextInput, - Loader, -} from "../components"; -import ContinueButton from "../components/ContinueButton"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { History } from "../../../schema/History"; -import { HistoryNode } from "../../../schema/HistoryNode"; -import StepContainer from "../components/StepContainer"; -import { useSelector } from "react-redux"; -import { RootStore } from "../redux/store"; -import useContinueWebsocket from "../hooks/useWebsocket"; -import useContinueNotebookProtocol from "../hooks/useWebsocket"; - -let TopNotebookDiv = styled.div` - display: grid; - grid-template-columns: 1fr; -`; - -let UserInputQueueItem = styled.div` - border-radius: ${defaultBorderRadius}; - color: gray; - padding: 8px; - margin: 8px; - text-align: center; -`; - -interface NotebookProps { - firstObservation?: any; -} - -function Notebook(props: NotebookProps) { - const [waitingForSteps, setWaitingForSteps] = useState(false); - const [userInputQueue, setUserInputQueue] = useState([]); - const [history, setHistory] = useState(); - // { - // timeline: [ - // { - // step: { - // name: "RunCodeStep", - // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // description: - // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - // }, - // output: [ - // { - // traceback: { - // frames: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // lineno: 7, - // function: "", - // code: "print(sum(first, second))", - // }, - // ], - // message: "unsupported operand type(s) for +: 'int' and 'str'", - // error_type: - // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - // full_traceback: - // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - // }, - // }, - // null, - // ], - // }, - // { - // step: { - // name: "EditCodeStep", - // range_in_files: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // range: { - // start: { - // line: 0, - // character: 0, - // }, - // end: { - // line: 6, - // character: 25, - // }, - // }, - // }, - // ], - // prompt: - // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", - // description: - // "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // }, - // output: [ - // null, - // { - // reversible: true, - // actions: [ - // { - // reversible: true, - // filesystem: {}, - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // range: { - // start: { - // line: 0, - // character: 0, - // }, - // end: { - // line: 6, - // character: 25, - // }, - // }, - // replacement: - // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", - // }, - // ], - // }, - // ], - // }, - // { - // step: { - // name: "SolveTracebackStep", - // traceback: { - // frames: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // lineno: 7, - // function: "", - // code: "print(sum(first, second))", - // }, - // ], - // message: "unsupported operand type(s) for +: 'int' and 'str'", - // error_type: - // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - // full_traceback: - // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - // }, - // description: "Running step: SolveTracebackStep", - // }, - // output: [null, null], - // }, - // { - // step: { - // name: "RunCodeStep", - // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // description: - // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - // }, - // output: [null, null], - // }, - // ], - // current_index: 0, - // } as any - // ); - - const client = useContinueNotebookProtocol(); - - useEffect(() => { - console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); - client?.onStateUpdate((state) => { - console.log("Received state update: ", state); - setWaitingForSteps(state.active); - setHistory(state.history); - setUserInputQueue(state.user_input_queue); - }); - }, [client]); - - const mainTextInputRef = useRef(null); - - useEffect(() => { - if (mainTextInputRef.current) { - mainTextInputRef.current.focus(); - let handler = (event: any) => { - if (event.data.type === "focusContinueInput") { - mainTextInputRef.current?.focus(); - } - }; - window.addEventListener("message", handler); - return () => { - window.removeEventListener("message", handler); - }; - } - }, [mainTextInputRef]); - - const onMainTextInput = () => { - if (mainTextInputRef.current) { - if (!client) return; - let input = mainTextInputRef.current.value; - setWaitingForSteps(true); - client.sendMainInput(input); - setUserInputQueue((queue) => { - return [...queue, input]; - }); - mainTextInputRef.current.value = ""; - mainTextInputRef.current.style.height = ""; - } - }; - - const onStepUserInput = (input: string, index: number) => { - if (!client) return; - console.log("Sending step user input", input, index); - client.sendStepUserInput(input, index); - }; - - // const iterations = useSelector(selectIterations); - return ( - - {typeof client === "undefined" && ( - <> - -

Server disconnected

- - )} - {history?.timeline.map((node: HistoryNode, index: number) => { - return ( - { - onStepUserInput(input, index); - }} - inFuture={index > history?.current_index} - historyNode={node} - onRefinement={(input: string) => { - client?.sendRefinementInput(input, index); - }} - onReverse={() => { - client?.reverseToIndex(index); - }} - /> - ); - })} - {waitingForSteps && } - -
- {userInputQueue.map((input) => { - return {input}; - })} -
- - { - if (e.key === "Enter") { - onMainTextInput(); - e.stopPropagation(); - e.preventDefault(); - } - }} - rows={1} - onChange={() => { - let textarea = mainTextInputRef.current!; - textarea.style.height = ""; /* Reset the height*/ - textarea.style.height = - Math.min(textarea.scrollHeight - 15, 500) + "px"; - }} - > - -
- ); -} - -export default Notebook; diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index f8f3c65a..40def480 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -59,10 +59,10 @@ export function activateExtension( }) ), ]).then(() => { - ideProtocolClient?.openNotebook(); + ideProtocolClient?.openGUI(); }); } else { - ideProtocolClient.openNotebook().then(() => { + ideProtocolClient.openGUI().then(() => { // openCapturedTerminal(); }); } diff --git a/extension/src/commands.ts b/extension/src/commands.ts index aeeb4b4f..f0c1744b 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -62,11 +62,11 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, "continue.openDebugPanel": () => { - ideProtocolClient.openNotebook(); + ideProtocolClient.openGUI(); }, "continue.focusContinueInput": async () => { if (!debugPanelWebview) { - await ideProtocolClient.openNotebook(); + await ideProtocolClient.openGUI(); } debugPanelWebview?.postMessage({ type: "focusContinueInput", diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 477d1420..ab890801 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -97,7 +97,7 @@ class IdeProtocolClient { this.openFile(data.filepath); // TODO: Close file break; - case "openNotebook": + case "openGUI": case "connected": break; default: @@ -133,17 +133,17 @@ class IdeProtocolClient { // ------------------------------------ // // Initiate Request - closeNotebook(sessionId: string) { + closeGUI(sessionId: string) { this.panels.get(sessionId)?.dispose(); this.panels.delete(sessionId); } - async openNotebook() { + async openGUI() { console.log("OPENING NOTEBOOK"); if (this.messenger === null) { console.log("MESSENGER IS NULL"); } - const resp = await this.messenger?.sendAndReceive("openNotebook", {}); + const resp = await this.messenger?.sendAndReceive("openGUI", {}); const sessionId = resp.sessionId; console.log("SESSION ID", sessionId); -- cgit v1.2.3-70-g09d2 From aea318b48dd7e15df16eca12ba59c677671869aa Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 13:44:33 -0400 Subject: policy + starcoder description fixes --- continuedev/src/continuedev/core/policy.py | 2 +- continuedev/src/continuedev/libs/steps/draft/dlt.py | 15 +++++++-------- continuedev/src/continuedev/libs/steps/main.py | 2 ++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/react-app/src/tabs/gui.tsx | 10 ++++++---- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 57363 -> 55662 bytes 7 files changed, 19 insertions(+), 16 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 9f68515f..6e264bab 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -18,7 +18,7 @@ class DemoPolicy(Policy): if history.get_current() is None: return MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() - observation = history.last_observation() + observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. if " test" in observation.user_input.lower(): diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index f3417c9d..9cec5014 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -50,14 +50,7 @@ class SetupPipelineStep(Step): class ValidatePipelineStep(Step): - - async def describe(self, models: Models): - return dedent("""\ - This step will validate that your dlt pipeline is working as expected: - - Test that the API call works - - Load the data into a local DuckDB instance - - Write a query to view the data - """) + hide: bool = True async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] @@ -111,5 +104,11 @@ class CreatePipelineStep(Step): - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> + MessageStep(message=dedent("""\ + This step will validate that your dlt pipeline is working as expected: + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data + """)) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index aefbe084..73ad3352 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -253,6 +253,8 @@ class StarCoderEditHighlightedCodeStep(Step): if completion.endswith(eot_token): completion = completion[:completion.rindex(eot_token)] + self._prompt_and_completion += prompt + completion + await sdk.ide.applyFileSystemEdit( FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) await sdk.ide.saveFile(rif.filepath) diff --git a/extension/package-lock.json b/extension/package-lock.json index ff4dbfa1..647e3aa2 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.11", + "version": "0.0.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.11", + "version": "0.0.13", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 8f1eabde..1219ca8e 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.11", + "version": "0.0.13", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 5ddddbfc..42ad4ed5 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -19,6 +19,7 @@ import useContinueGUIProtocol from "../hooks/useWebsocket"; let TopGUIDiv = styled.div` display: grid; grid-template-columns: 1fr; + overflow: scroll; `; let UserInputQueueItem = styled.div` @@ -37,7 +38,7 @@ function GUI(props: GUIProps) { const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [history, setHistory] = useState(); - // { + // { // timeline: [ // { // step: { @@ -153,8 +154,7 @@ function GUI(props: GUIProps) { // }, // ], // current_index: 0, - // } as any - // ); + // } as any); const client = useContinueGUIProtocol(); @@ -211,7 +211,9 @@ function GUI(props: GUIProps) { {typeof client === "undefined" && ( <> -

Server disconnected

+

+ Trying to reconnect with server... +

)} {history?.timeline.map((node: HistoryNode, index: number) => { diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 6cda6f2d..fd3f48d1 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From 529279abf16cc8d7f2e4340819b9a8e945202185 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 00:21:52 -0400 Subject: forgotten changes --- extension/package-lock.json | 4 ++-- extension/package.json | 4 ++-- extension/react-app/src/components/VSCodeFileLink.tsx | 4 ++-- extension/react-app/src/tabs/chat/MessageDiv.tsx | 2 +- extension/scripts/continuedev-0.1.1-py3-none-any.whl | Bin 0 -> 56962 bytes 5 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 extension/scripts/continuedev-0.1.1-py3-none-any.whl (limited to 'extension/react-app') diff --git a/extension/package-lock.json b/extension/package-lock.json index 15ea6428..2db9cac5 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.14", + "version": "0.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.14", + "version": "0.0.16", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 2d83a58c..13086954 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.14", + "version": "0.0.16", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -161,7 +161,7 @@ "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", - "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", + "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.1-py3-none-any.whl ../extension/scripts/continuedev-0.1.1-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.8.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx index 12ce5af8..6219654d 100644 --- a/extension/react-app/src/components/VSCodeFileLink.tsx +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -1,7 +1,7 @@ import React from "react"; import { postVscMessage } from "../vscode"; -function VS CodeFileLink(props: { path: string; text?: string }) { +function VSCodeFileLink(props: { path: string; text?: string }) { return (
Date: Sat, 3 Jun 2023 11:33:05 -0400 Subject: API tokens set through global Vsc settings --- continuedev/src/continuedev/core/sdk.py | 20 +------------ continuedev/src/continuedev/server/ide.py | 35 ++++++++++------------ continuedev/src/continuedev/server/ide_protocol.py | 4 +++ continuedev/src/continuedev/steps/core/core.py | 1 + continuedev/src/continuedev/steps/main.py | 2 +- extension/package.json | 10 +++++++ .../react-app/src/components/StepContainer.tsx | 10 +++---- extension/src/continueIdeClient.ts | 27 +++++++++++++++++ 8 files changed, 64 insertions(+), 45 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 5d0f03fe..5ae471c4 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -11,7 +11,6 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..steps.core.core import * -from .env import get_env_var, make_sure_env_exists class Autopilot: @@ -105,24 +104,7 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - make_sure_env_exists() - - val = None - while val is None: - try: - val = get_env_var(env_var) - if val is not None: - return val - except: - pass - server_dir = os.getcwd() - env_path = os.path.join(server_dir, ".env") - await self.ide.setFileOpen(env_path) - await self.append_to_file(env_path, f'\n{env_var}=""') - await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) - val = get_env_var(env_var) - - return val + return await self.ide.getUserSecret(env_var) async def get_config(self) -> ContinueConfig: dir = await self.ide.getWorkspaceDirectory() diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 71017ce0..eec5b607 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -40,46 +40,42 @@ Server.handle_exit = AppStatus.handle_exit class FileEditsUpdate(BaseModel): - messageType: str = "fileEdits" fileEdits: List[FileEditWithFullContents] class OpenFilesResponse(BaseModel): - messageType: str = "openFiles" openFiles: List[str] class HighlightedCodeResponse(BaseModel): - messageType: str = "highlightedCode" highlightedCode: List[RangeInFile] class ShowSuggestionRequest(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit class ShowSuggestionResponse(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit accepted: bool class ReadFileResponse(BaseModel): - messageType: str = "readFile" contents: str class EditFileResponse(BaseModel): - messageType: str = "editFile" fileEdit: FileEditWithFullContents class WorkspaceDirectoryResponse(BaseModel): - messageType: str = "workspaceDirectory" workspaceDirectory: str +class GetUserSecretResponse(BaseModel): + value: str + + T = TypeVar("T", bound=BaseModel) @@ -114,7 +110,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret"]: self.sub_queue.post(message_type, data) else: raise ValueError("Unknown message type", message_type) @@ -183,31 +179,31 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: - resp = await self._send_and_receive_json({ - "messageType": "openFiles" - }, OpenFilesResponse, "openFiles") + resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles") return resp.openFiles async def getWorkspaceDirectory(self) -> str: - resp = await self._send_and_receive_json({ - "messageType": "workspaceDirectory" - }, WorkspaceDirectoryResponse, "workspaceDirectory") + resp = await self._send_and_receive_json({}, WorkspaceDirectoryResponse, "workspaceDirectory") return resp.workspaceDirectory async def getHighlightedCode(self) -> List[RangeInFile]: - resp = await self._send_and_receive_json({ - "messageType": "highlightedCode" - }, HighlightedCodeResponse, "highlightedCode") + resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode") return resp.highlightedCode async def readFile(self, filepath: str) -> str: """Read a file""" resp = await self._send_and_receive_json({ - "messageType": "readFile", "filepath": filepath }, ReadFileResponse, "readFile") return resp.contents + async def getUserSecret(self, key: str) -> str: + """Get a user secret""" + resp = await self._send_and_receive_json({ + "key": key + }, GetUserSecretResponse, "getUserSecret") + return resp.value + async def saveFile(self, filepath: str): """Save a file""" await self._send_json("saveFile", { @@ -222,7 +218,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def editFile(self, edit: FileEdit) -> FileEditWithFullContents: """Edit a file""" resp = await self._send_and_receive_json({ - "messageType": "editFile", "edit": edit.dict() }, EditFileResponse, "editFile") return resp.fileEdit diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 4f505e80..8f155415 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -78,3 +78,7 @@ class AbstractIdeProtocolServer(ABC): @abstractmethod async def saveFile(self, filepath: str): """Save a file""" + + @abstractmethod + async def getUserSecret(self, key: str): + """Get a user secret""" diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index e54a9a21..0f513f3e 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -205,4 +205,5 @@ class WaitForUserConfirmationStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: self._description = self.prompt resp = await sdk.wait_for_user_input() + self.hide = True return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index bb720b20..dfb4f3be 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -190,7 +190,7 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - name: str = "Editing code" + name: str = "Editing Code" hide = False _prompt: str = "{code}{user_request}" diff --git a/extension/package.json b/extension/package.json index 13086954..cc8e18c4 100644 --- a/extension/package.json +++ b/extension/package.json @@ -39,6 +39,16 @@ "type": "string", "default": "http://localhost:8000", "description": "The URL of the Continue server to use." + }, + "continue.OPENAI_API_KEY": { + "type": "string", + "default": "", + "description": "The OpenAI API key to use for code generation." + }, + "continue.HUGGING_FACE_TOKEN": { + "type": "string", + "default": "", + "description": "The Hugging Face API token to use for code generation." } } }, diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 5e979b34..fd29f21b 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,12 +114,12 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden" + // onClick={() => setOpen((prev) => !prev)} > -

+

{open ? ( ) : ( @@ -127,14 +127,14 @@ function StepContainer(props: StepContainerProps) { )} {props.historyNode.step.name as any}:

- { e.stopPropagation(); props.onReverse(); }} > - + */}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 03e5fbc5..a5a1c5dc 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -74,6 +74,12 @@ class IdeProtocolClient { this.messenger?.send("workspaceDirectory", { workspaceDirectory: this.getWorkspaceDirectory(), }); + break; + case "getUserSecret": + this.messenger?.send("getUserSecret", { + value: await this.getUserSecret(data.key), + }); + break; case "openFiles": this.messenger?.send("openFiles", { openFiles: this.getOpenFiles(), @@ -130,6 +136,27 @@ class IdeProtocolClient { openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One); } + async getUserSecret(key: string) { + // Check if secret already exists in VS Code settings (global) + let secret = vscode.workspace.getConfiguration("continue").get(key); + if (secret && secret !== "") return secret; + + // If not, ask user for secret + while (typeof secret === "undefined" || secret === "") { + secret = await vscode.window.showInputBox({ + prompt: `Enter secret for ${key}`, + password: true, + }); + } + + // Add secret to VS Code settings + vscode.workspace + .getConfiguration("continue") + .update(key, secret, vscode.ConfigurationTarget.Global); + + return secret; + } + // ------------------------------------ // // Initiate Request -- cgit v1.2.3-70-g09d2 From 4f3ceee573268fbe9db80fea372198523b5757a6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 11:56:09 -0400 Subject: show step details on toggle --- continuedev/src/continuedev/steps/core/core.py | 6 ++++++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- .../react-app/src/components/StepContainer.tsx | 16 ++++++++++++---- .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 56962 -> 56858 bytes 5 files changed, 21 insertions(+), 7 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 0f513f3e..d7f7a307 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -43,7 +43,12 @@ class ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" + _err_text: str | None = None + async def describe(self, models: Models) -> Coroutine[str, None, None]: + if self._err_text is not None: + return f"Error when running shell commands:\n```\n{self._err_text}\n```" + cmds_str = "\n".join(self.cmds) return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") @@ -58,6 +63,7 @@ class ShellCommandsStep(Step): # If it fails, return the error if err is not None and err != "": + self._err_text = err return TextObservation(text=err) return None diff --git a/extension/package-lock.json b/extension/package-lock.json index 2db9cac5..32f47246 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index cc8e18c4..be10be80 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.16", + "version": "0.0.17", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index fd29f21b..f962cbc9 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,8 +114,8 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden cursor-pointer" + onClick={() => setOpen((prev) => !prev)} > @@ -137,6 +137,14 @@ function StepContainer(props: StepContainerProps) { */} + {open && ( +
+              Step Details:
+              
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} + {props.historyNode.step.description as any} @@ -181,7 +189,7 @@ function StepContainer(props: StepContainerProps) {
- */} ); } diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 663eb14a..d143bcf9 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From b8067876bc5dd425d491863bd5338f325fea35ed Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 14:20:21 -0400 Subject: error handling and step retry --- continuedev/src/continuedev/core/agent.py | 180 --------------------- continuedev/src/continuedev/core/autopilot.py | 52 +++--- continuedev/src/continuedev/core/main.py | 9 +- continuedev/src/continuedev/core/observation.py | 4 + .../src/continuedev/libs/llm/hf_inference_api.py | 9 +- continuedev/src/continuedev/server/gui.py | 6 + continuedev/src/continuedev/server/gui_protocol.py | 4 + .../react-app/src/components/StepContainer.tsx | 43 ++++- .../react-app/src/hooks/useContinueGUIProtocol.ts | 4 + extension/react-app/src/tabs/gui.tsx | 4 + 10 files changed, 106 insertions(+), 209 deletions(-) delete mode 100644 continuedev/src/continuedev/core/agent.py (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py deleted file mode 100644 index 1996abb1..00000000 --- a/continuedev/src/continuedev/core/agent.py +++ /dev/null @@ -1,180 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from .sdk import ContinueSDK - - -class Autopilot(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - return user_input - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) - - self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Autopilot is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 85f65dc3..db06c975 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -3,7 +3,7 @@ import time from typing import Callable, Coroutine, List from ..models.filesystem_edit import FileEditWithFullContents from ..libs.llm import LLM -from .observation import Observation +from .observation import Observation, InternalErrorObservation from ..server.ide_protocol import AbstractIdeProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..models.main import ContinueBaseModel @@ -77,6 +77,11 @@ class Autopilot(ContinueBaseModel): _step_depth: int = 0 + async def retry_at_index(self, index: int): + last_step = self.history.pop_last_step() + await self.update_subscribers() + await self._run_singular_step(last_step) + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: capture_event( 'step run', {'step_name': step.name, 'params': step.dict()}) @@ -96,14 +101,28 @@ class Autopilot(ContinueBaseModel): # Call all subscribed callbacks await self.update_subscribers() - # Run step + # Try to run step and handle errors self._step_depth += 1 - observation = await step(ContinueSDK(self)) + + try: + observation = await step(ContinueSDK(self)) + except Exception as e: + # Attach an InternalErrorObservation to the step and unhide it. + error_string = '\n\n'.join( + traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}" + print( + f"Error while running step: \n{error_string}\n{e}") + + observation = InternalErrorObservation( + error=error_string) + step.hide = False + self._step_depth -= 1 # Add observation to history self.history.get_last_at_depth( self._step_depth, include_current=True).observation = observation + await self.update_subscribers() # Update its description async def update_description(): @@ -122,22 +141,17 @@ class Autopilot(ContinueBaseModel): next_step = step is_future_step = False while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + await self._run_singular_step(next_step, is_future_step) + + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: next_step = None self._active = False diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index a2336671..b2b97bae 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -67,6 +67,13 @@ class History(ContinueBaseModel): return None return state.observation + def pop_last_step(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + node = self.timeline.pop(self.current_index) + self.current_index -= 1 + return node.step + @classmethod def from_empty(cls): return cls(timeline=[], current_index=-1) @@ -118,7 +125,7 @@ class Step(ContinueBaseModel): if self._description is not None: d["description"] = self._description else: - d["description"] = self.name + d["description"] = "`Description loading...`" return d @validator("name", pre=True, always=True) diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py index fef04311..b6117236 100644 --- a/continuedev/src/continuedev/core/observation.py +++ b/continuedev/src/continuedev/core/observation.py @@ -33,3 +33,7 @@ class TextObservation(Observation): if v is None: return "" return v + + +class InternalErrorObservation(Observation): + error: str diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py index 83852d27..734da160 100644 --- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -22,4 +22,11 @@ class HuggingFaceInferenceAPI(LLM): "return_full_text": False, } }) - return response.json()[0]["generated_text"] + data = response.json() + + # Error if the response is not a list + if not isinstance(data, list): + raise Exception( + "Hugging Face returned an error response: \n\n", data) + + return data[0]["generated_text"] diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 3d1a5a82..b873a88f 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -75,6 +75,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.on_refinement_input(data["input"], data["index"]) elif message_type == "reverse_to_index": self.on_reverse_to_index(data["index"]) + elif message_type == "retry_at_index": + self.on_retry_at_index(data["index"]) except Exception as e: print(e) @@ -100,6 +102,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer): asyncio.create_task( self.session.autopilot.accept_refinement_input(input, index)) + def on_retry_at_index(self, index: int): + asyncio.create_task( + self.session.autopilot.retry_at_index(index)) + @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index e32d80ef..287f9e3b 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -26,3 +26,7 @@ class AbstractGUIProtocolServer(ABC): @abstractmethod async def send_state_update(self, state: dict): """Send a state update to the client""" + + @abstractmethod + def on_retry_at_index(self, index: int): + """Called when the user requests a retry at a previous index""" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index f962cbc9..903f9b94 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -18,6 +18,7 @@ import { ChevronDown, ChevronRight, Backward, + ArrowPath, } from "@styled-icons/heroicons-outline"; import { HistoryNode } from "../../../schema/HistoryNode"; import ReactMarkdown from "react-markdown"; @@ -29,6 +30,7 @@ interface StepContainerProps { inFuture: boolean; onRefinement: (input: string) => void; onUserInput: (input: string) => void; + onRetry: () => void; } const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` @@ -135,19 +137,44 @@ function StepContainer(props: StepContainerProps) { > */} + + {props.historyNode.observation?.error ? ( + { + e.stopPropagation(); + props.onRetry(); + }} + > + + + ) : ( + <> + )} {open && ( -
-              Step Details:
-              
- {JSON.stringify(props.historyNode.step, null, 2)} -
+ <> +
+                Step Details:
+                
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} - - {props.historyNode.step.description as any} - + {props.historyNode.observation?.error ? ( + <> + Error while running step: +
+
+                {props.historyNode.observation.error as string}
+              
+ + ) : ( + + {props.historyNode.step.description as any} + + )} {props.historyNode.step.name === "Waiting for user input" && ( { client?.reverseToIndex(index); }} + onRetry={() => { + client?.retryAtIndex(index); + setWaitingForSteps(true); + }} /> ); })} -- cgit v1.2.3-70-g09d2 From 9613750fd1ff48343e8d80ce5154733a4dbc55d7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 5 Jun 2023 12:09:13 -0400 Subject: Enter button on user input --- continuedev/src/continuedev/core/policy.py | 5 +- .../recipes/CreatePipelineRecipe/main.py | 9 ++- continuedev/src/continuedev/steps/core/core.py | 7 +- .../react-app/src/components/ContinueButton.tsx | 8 ++- .../react-app/src/components/InputAndButton.tsx | 77 ++++++++++++++++++++++ .../react-app/src/components/StepContainer.tsx | 20 ++---- extension/react-app/src/tabs/gui.tsx | 6 +- 7 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 extension/react-app/src/components/InputAndButton.tsx (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 4287bb6e..e65b6c9d 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -17,7 +17,10 @@ class DemoPolicy(Policy): def next(self, history: History) -> Step: # At the very start, run initial Steps spcecified in the config if history.get_current() is None: - return MessageStep(message="Welcome to Continue!") >> SetupContinueWorkspaceStep() >> CreateCodebaseIndexChroma() >> StepsOnStartupStep() + return (MessageStep(message="Welcome to Continue!") >> + SetupContinueWorkspaceStep() >> + CreateCodebaseIndexChroma() >> + StepsOnStartupStep()) observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 4a4604d6..55c25da4 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -11,7 +11,7 @@ class CreatePipelineRecipe(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - await sdk.run_step( + text_observation = await sdk.run_step( MessageStep(message=dedent("""\ This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: - Create a Python virtual environment with dlt installed @@ -21,7 +21,10 @@ class CreatePipelineRecipe(Step): - Test that the API call works - Load the data into a local DuckDB instance - Write a query to view the data""")) >> - WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep(api_description="WeatherAPI.com API") >> + WaitForUserInputStep( + prompt="What API do you want to load data from?") + ) + await sdk.run_step( + SetupPipelineStep(api_description=text_observation.text) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d7f7a307..04446787 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -191,13 +191,18 @@ class WaitForUserInputStep(Step): name: str = "Waiting for user input" _description: Union[str, None] = None + _response: Union[str, None] = None async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.prompt + if self._response is None: + return self.prompt + else: + return self.prompt + "\n\n" + self._response async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: self._description = self.prompt resp = await sdk.wait_for_user_input() + self._response = resp return TextObservation(text=resp) diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index 11dc7a92..c6117bf9 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -24,9 +24,13 @@ let StyledButton = styled(Button)` } `; -function ContinueButton(props: { onClick?: () => void }) { +function ContinueButton(props: { onClick?: () => void; hidden?: boolean }) { return ( - +
+              
                 Step Details:
                 
{JSON.stringify(props.historyNode.step, null, 2)} diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index bbaf5f08..42671ade 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -176,7 +176,7 @@ class IdeProtocolClient { // If not, ask user for secret while (typeof secret === "undefined" || secret === "") { secret = await vscode.window.showInputBox({ - prompt: `Enter secret for ${key}`, + prompt: `Enter secret for ${key}. You can edit this later in the Continue VS Code settings.`, password: true, }); } -- cgit v1.2.3-70-g09d2 From cc0d73c2c1351a08c95654f7792f56c1d3d0ab54 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 12 Jun 2023 21:30:49 -0700 Subject: slash commands dropdown! --- continuedev/src/continuedev/core/abstract_sdk.py | 4 +- continuedev/src/continuedev/core/autopilot.py | 9 +- continuedev/src/continuedev/core/config.py | 34 ++- continuedev/src/continuedev/core/main.py | 6 +- continuedev/src/continuedev/core/policy.py | 73 +---- continuedev/src/continuedev/core/sdk.py | 6 +- .../continuedev/libs/util/step_name_to_steps.py | 27 ++ .../recipes/CreatePipelineRecipe/main.py | 2 +- .../recipes/CreatePipelineRecipe/steps.py | 3 +- continuedev/src/continuedev/server/gui.py | 7 + continuedev/src/continuedev/server/gui_protocol.py | 6 +- continuedev/src/continuedev/steps/main.py | 4 +- .../src/continuedev/steps/steps_on_startup.py | 24 +- extension/package-lock.json | 64 ++++- extension/package.json | 3 +- extension/react-app/package-lock.json | 60 +++++ extension/react-app/package.json | 1 + extension/react-app/src/components/ComboBox.tsx | 146 ++++++++++ .../src/hooks/ContinueGUIClientProtocol.ts | 4 + .../react-app/src/hooks/useContinueGUIProtocol.ts | 10 + extension/react-app/src/tabs/gui.tsx | 296 +++++++++++---------- .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 74791 -> 78916 bytes 22 files changed, 547 insertions(+), 242 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/step_name_to_steps.py create mode 100644 extension/react-app/src/components/ComboBox.tsx (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py index 3b85708d..0658f1b8 100644 --- a/continuedev/src/continuedev/core/abstract_sdk.py +++ b/continuedev/src/continuedev/core/abstract_sdk.py @@ -76,8 +76,8 @@ class AbstractContinueSDK(ABC): async def get_user_secret(self, env_var: str, prompt: str) -> str: pass - @abstractmethod - async def get_config(self) -> ContinueConfig: + @abstractproperty + def config(self) -> ContinueConfig: pass @abstractmethod diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 1642003c..0874bbc5 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -40,6 +40,9 @@ class Autopilot(ContinueBaseModel): def get_full_state(self) -> FullState: return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + async def get_available_slash_commands(self) -> List[Dict]: + return list(map(lambda x: {"name": x.name, "description": x.description}, self.continue_sdk.config.slash_commands)) or [] + async def clear_history(self): self.history = History.from_empty() self._main_user_input_queue = [] @@ -202,7 +205,7 @@ class Autopilot(ContinueBaseModel): await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): + if next_step := self.policy.next(self.continue_sdk.config, self.history): is_future_step = False elif next_step := self.history.take_next_step(): is_future_step = True @@ -215,11 +218,11 @@ class Autopilot(ContinueBaseModel): await self.update_subscribers() async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) + next_step = self.policy.next(self.continue_sdk.config, self.history) await self.run_from_step(next_step) async def run_policy(self): - first_step = self.policy.next(self.history) + first_step = self.policy.next(self.continue_sdk.config, self.history) await self.run_from_step(first_step) async def _request_halt(self): diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 8ed41a82..cf723984 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -1,9 +1,18 @@ import json import os -from pydantic import BaseModel +from pydantic import BaseModel, validator from typing import List, Optional, Dict import yaml +from .main import Step + + +class SlashCommand(BaseModel): + name: str + description: str + step_name: str + params: Optional[Dict] = {} + class ContinueConfig(BaseModel): """ @@ -12,6 +21,29 @@ class ContinueConfig(BaseModel): steps_on_startup: Optional[Dict[str, Dict]] = {} server_url: Optional[str] = None allow_anonymous_telemetry: Optional[bool] = True + slash_commands: Optional[List[SlashCommand]] = [ + # SlashCommand( + # name="pytest", + # description="Write pytest unit tests for the current file", + # step_name="WritePytestsRecipe", + # params=??) + + SlashCommand( + name="dlt", + description="Create a dlt pipeline", + step_name="CreatePipelineRecipe", + ), + SlashCommand( + name="ddtobq", + description="Create a dlt pipeline to load data from a data source into BigQuery", + step_name="DDtoBQRecipe", + ), + SlashCommand( + name="deployairflow", + description="Deploy a dlt pipeline to Airflow", + step_name="DeployPipelineAirflowRecipe", + ), + ] def load_config(config_file: str) -> ContinueConfig: diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 81aaaf2e..f6b26d69 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -118,11 +118,15 @@ class Models: pass +class ContinueConfig: + pass + + class Policy(ContinueBaseModel): """A rule that determines which step to take next""" # Note that history is mutable, kinda sus - def next(self, history: History = History.from_empty()) -> "Step": + def next(self, config: ContinueConfig, history: History = History.from_empty()) -> "Step": raise NotImplementedError diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 00b5427c..37a10e36 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,5 +1,6 @@ from typing import List, Tuple, Type +from .config import ContinueConfig from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma from ..steps.steps_on_startup import StepsOnStartupStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe @@ -15,12 +16,13 @@ from ..steps.react import NLDecisionStep from ..steps.chat import SimpleChatStep from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..steps.core.core import MessageStep +from ..libs.util.step_name_to_steps import get_step_from_name class DemoPolicy(Policy): ran_code_last: bool = False - def next(self, history: History) -> Step: + def next(self, config: ContinueConfig, history: History) -> Step: # At the very start, run initial Steps spcecified in the config if history.get_current() is None: return ( @@ -33,20 +35,18 @@ class DemoPolicy(Policy): if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. user_input = observation.user_input + + if user_input.startswith("/"): + command_name = user_input.split(" ")[0] + after_command = " ".join(user_input.split(" ")[1:]) + for slash_command in config.slash_commands: + if slash_command.name == command_name[1:]: + return get_step_from_name(slash_command.step_name, slash_command.params) + if "/pytest" in user_input.lower(): return WritePytestsRecipe(instructions=user_input) - elif "/dlt" in user_input.lower() or " dlt" in user_input.lower(): - return CreatePipelineRecipe() if "/pytest" in observation.user_input.lower(): return WritePytestsRecipe(instructions=observation.user_input) - elif "/dlt" in observation.user_input.lower(): - return CreatePipelineRecipe() - elif "/ddtobq" in observation.user_input.lower(): - return DDtoBQRecipe() - elif "/airflow" in observation.user_input.lower(): - return DeployPipelineAirflowRecipe() - elif "/transform" in observation.user_input.lower(): - return AddTransformRecipe() elif "/comment" in observation.user_input.lower(): return CommentCodeStep() elif "/ask" in user_input: @@ -72,54 +72,3 @@ class DemoPolicy(Policy): return SolveTracebackStep(traceback=observation.traceback) else: return None - - -class ObservationTypePolicy(Policy): - def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): - self.observation_type = observation_type - self.step_type = step_type - self.base_policy = base_policy - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, self.observation_type): - return self.step_type(observation) - return self.base_policy.next(history) - - -class PolicyWrappedWithValidators(Policy): - """Default is to stop, unless the validator tells what to do next""" - index: int - stage: int - - def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): - # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. - self.pairs = pairs - self.index = len(pairs) - self.validating = 0 - self.base_policy = base_policy - - def next(self, history: History) -> Step: - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - - if self.stage == 0: - # Running the validator at the current index for the first time - validator, step = self.pairs[self.index] - self.stage = 1 - return validator - elif self.stage == 1: - # Previously ran the validator at the current index, now receiving its ValidatorObservation - observation = history.last_observation() - if observation.passed: - self.stage = 0 - self.index += 1 - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - else: - return self.pairs[self.index][0] - else: - _, step_type = self.pairs[self.index] - return step_type(observation) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 2849b0c8..1f4cdfb2 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod import asyncio from functools import cached_property from typing import Coroutine, Union @@ -119,8 +118,9 @@ class ContinueSDK(AbstractContinueSDK): async def get_user_secret(self, env_var: str, prompt: str) -> str: return await self.ide.getUserSecret(env_var) - async def get_config(self) -> ContinueConfig: - dir = await self.ide.getWorkspaceDirectory() + @property + def config(self) -> ContinueConfig: + dir = self.ide.workspace_directory yaml_path = os.path.join(dir, '.continue', 'config.yaml') json_path = os.path.join(dir, '.continue', 'config.json') if os.path.exists(yaml_path): diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py new file mode 100644 index 00000000..4023b73b --- /dev/null +++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py @@ -0,0 +1,27 @@ +from typing import Dict + +from ...core.main import Step +from ...steps.core.core import UserInputStep +from ...recipes.CreatePipelineRecipe.main import CreatePipelineRecipe +from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe +from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe +from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe +from ...recipes.AddTransformRecipe.main import AddTransformRecipe + +step_name_to_step_class = { + "UserInputStep": UserInputStep, + "CreatePipelineRecipe": CreatePipelineRecipe, + "DDtoBQRecipe": DDtoBQRecipe, + "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe, + "AddTransformRecipe": AddTransformRecipe, + "DDtoBQRecipe": DDtoBQRecipe +} + + +def get_step_from_name(step_name: str, params: Dict) -> Step: + try: + return step_name_to_step_class[step_name](**params) + except: + print( + f"Incorrect parameters for step {step_name}. Parameters provided were: {params}") + raise diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 818168ba..92bddc98 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -1,7 +1,7 @@ from textwrap import dedent -from ...core.main import Step from ...core.sdk import ContinueSDK +from ...core.main import Step from ...steps.core.core import WaitForUserInputStep from ...steps.core.core import MessageStep from .steps import SetupPipelineStep, ValidatePipelineStep, RunQueryStep diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index e59cc51c..ea4607da 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -6,11 +6,10 @@ import time from ...models.main import Range from ...models.filesystem import RangeInFile from ...steps.core.core import MessageStep -from ...core.sdk import Models from ...core.observation import DictObservation, InternalErrorObservation from ...models.filesystem_edit import AddFile, FileEdit from ...core.main import Step -from ...core.sdk import ContinueSDK +from ...core.sdk import ContinueSDK, Models AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)" diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index e8b52004..cf046734 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -90,6 +90,12 @@ class GUIProtocolServer(AbstractGUIProtocolServer): "state": state }) + async def send_available_slash_commands(self): + commands = await self.session.autopilot.get_available_slash_commands() + await self._send_json("available_slash_commands", { + "commands": commands + }) + def on_main_input(self, input: str): # Do something with user input asyncio.create_task(self.session.autopilot.accept_user_input(input)) @@ -127,6 +133,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we protocol.websocket = websocket # Update any history that may have happened before connection + await protocol.send_available_slash_commands() await protocol.send_state_update() while AppStatus.should_exit is False: diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index 889c6761..d9506c6f 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Dict, List from abc import ABC, abstractmethod @@ -27,6 +27,10 @@ class AbstractGUIProtocolServer(ABC): async def send_state_update(self, state: dict): """Send a state update to the client""" + @abstractmethod + async def send_available_slash_commands(self, commands: List[Dict]): + """Send a list of available slash commands to the client""" + @abstractmethod def on_retry_at_index(self, index: int): """Called when the user requests a retry at a previous index""" diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 9634c726..36e4f519 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -63,10 +63,10 @@ class RunPolicyUntilDoneStep(Step): policy: "Policy" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - next_step = self.policy.next(sdk.history) + next_step = self.policy.next(sdk.config, sdk.history) while next_step is not None: observation = await sdk.run_step(next_step) - next_step = self.policy.next(sdk.history) + next_step = self.policy.next(sdk.config, sdk.history) return observation diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py index eae8b558..365cbe1a 100644 --- a/continuedev/src/continuedev/steps/steps_on_startup.py +++ b/continuedev/src/continuedev/steps/steps_on_startup.py @@ -1,19 +1,12 @@ -from ..core.main import ContinueSDK, Models, Step +from ..core.main import Step +from ..core.sdk import Models, ContinueSDK from .main import UserInputStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..recipes.AddTransformRecipe.main import AddTransformRecipe - -step_name_to_step_class = { - "UserInputStep": UserInputStep, - "CreatePipelineRecipe": CreatePipelineRecipe, - "DDtoBQRecipe": DDtoBQRecipe, - "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe, - "AddTransformRecipe": AddTransformRecipe, - "DDtoBQRecipe": DDtoBQRecipe -} +from ..libs.util.step_name_to_steps import get_step_from_name class StepsOnStartupStep(Step): @@ -23,13 +16,8 @@ class StepsOnStartupStep(Step): return "Running steps on startup" async def run(self, sdk: ContinueSDK): - steps_descriptions = (await sdk.get_config()).steps_on_startup + steps_on_startup = sdk.config.steps_on_startup - for step_name, step_params in steps_descriptions.items(): - try: - step = step_name_to_step_class[step_name](**step_params) - except: - print( - f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") - continue + for step_name, step_params in steps_on_startup.items(): + step = get_step_from_name(step_name, step_params) await sdk.run_step(step) diff --git a/extension/package-lock.json b/extension/package-lock.json index aebd0803..b02c4544 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.34", + "version": "0.0.35", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.34", + "version": "0.0.35", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", @@ -15,6 +15,7 @@ "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", + "downshift": "^7.6.0", "highlight.js": "^11.7.0", "posthog-js": "^1.63.3", "react-markdown": "^8.0.7", @@ -2845,6 +2846,11 @@ "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", "dev": true }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3261,6 +3267,31 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/downshift/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "node_modules/dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -10615,6 +10646,11 @@ "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", "dev": true }, + "compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -10911,6 +10947,30 @@ "domhandler": "^5.0.1" } }, + "downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "requires": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + } + } + }, "dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", diff --git a/extension/package.json b/extension/package.json index 8ee8cb4c..8ccb4b13 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.34", + "version": "0.0.35", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -134,6 +134,7 @@ "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", + "downshift": "^7.6.0", "highlight.js": "^11.7.0", "posthog-js": "^1.63.3", "react-markdown": "^8.0.7", diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index dbcbc5cc..64440da6 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "downshift": "^7.6.0", "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1288,6 +1289,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -1405,6 +1411,26 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/electron-to-chromium": { "version": "1.4.311", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", @@ -3003,6 +3029,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -4050,6 +4081,11 @@ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, + "compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -4131,6 +4167,25 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "requires": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "electron-to-chromium": { "version": "1.4.311", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", @@ -5138,6 +5193,11 @@ "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" }, + "tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 7d1211de..a53fbec8 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -11,6 +11,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "downshift": "^7.6.0", "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx new file mode 100644 index 00000000..1b7c60e6 --- /dev/null +++ b/extension/react-app/src/components/ComboBox.tsx @@ -0,0 +1,146 @@ +import React, { useCallback } from "react"; +import { useCombobox } from "downshift"; +import styled from "styled-components"; +import { + buttonColor, + defaultBorderRadius, + secondaryDark, + vscBackground, +} from "."; + +const mainInputFontSize = 16; +const MainTextInput = styled.input` + padding: 8px; + font-size: ${mainInputFontSize}px; + border-radius: ${defaultBorderRadius}; + border: 1px solid #ccc; + margin: 8px auto; + width: 100%; + background-color: ${vscBackground}; + color: white; + outline: 1px solid orange; +`; + +const UlMaxHeight = 200; +const Ul = styled.ul<{ + hidden: boolean; + showAbove: boolean; + ulHeightPixels: number; +}>` + ${(props) => + props.showAbove + ? `transform: translateY(-${props.ulHeightPixels + 8}px);` + : `transform: translateY(${2 * mainInputFontSize}px);`} + position: absolute; + background: ${vscBackground}; + background-color: ${secondaryDark}; + color: white; + font-family: "Fira Code", monospace; + max-height: ${UlMaxHeight}px; + overflow: scroll; + padding: 0; + ${({ hidden }) => hidden && "display: none;"} + border-radius: ${defaultBorderRadius}; + overflow: hidden; + border: 0.5px solid gray; +`; + +const Li = styled.li<{ + highlighted: boolean; + selected: boolean; + isLastItem: boolean; +}>` + ${({ highlighted }) => highlighted && "background: #aa0000;"} + ${({ selected }) => selected && "font-weight: bold;"} + padding: 0.5rem 0.75rem; + display: flex; + flex-direction: column; + ${({ isLastItem }) => isLastItem && "border-bottom: 1px solid gray;"} + border-top: 1px solid gray; + cursor: pointer; +`; + +interface ComboBoxProps { + items: { name: string; description: string }[]; + onInputValueChange: (inputValue: string) => void; + disabled?: boolean; + onEnter?: (e: React.KeyboardEvent) => void; +} + +const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { + const [items, setItems] = React.useState(props.items); + const { + isOpen, + getToggleButtonProps, + getLabelProps, + getMenuProps, + getInputProps, + highlightedIndex, + getItemProps, + selectedItem, + setInputValue, + } = useCombobox({ + onInputValueChange({ inputValue }) { + if (!inputValue) return; + props.onInputValueChange(inputValue); + setItems( + props.items.filter((item) => + item.name.toLowerCase().startsWith(inputValue.toLowerCase()) + ) + ); + }, + items, + itemToString(item) { + return item ? item.name : ""; + }, + }); + + const divRef = React.useRef(null); + const ulRef = React.useRef(null); + const showAbove = () => { + return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; + }; + + return ( + + ); +}); + +export default ComboBox; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 71303c70..824bb086 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -9,6 +9,10 @@ abstract class AbstractContinueGUIClientProtocol { abstract onStateUpdate(state: any): void; + abstract onAvailableSlashCommands( + callback: (commands: { name: string; description: string }[]) => void + ): void; + abstract sendClear(): void; abstract retryAtIndex(index: number): void; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index a8e28fc5..59397742 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -45,6 +45,16 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { }); } + onAvailableSlashCommands( + callback: (commands: { name: string; description: string }[]) => void + ) { + this.messenger.onMessageType("available_slash_commands", (data: any) => { + if (data.commands) { + callback(data.commands); + } + }); + } + sendClear() { this.messenger.send("clear_history", {}); } diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index cb7a5440..1569c178 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -12,7 +12,8 @@ import { History } from "../../../schema/History"; import { HistoryNode } from "../../../schema/HistoryNode"; import StepContainer from "../components/StepContainer"; import useContinueGUIProtocol from "../hooks/useWebsocket"; -import { Trash } from "@styled-icons/heroicons-outline"; +import { BookOpen, Trash } from "@styled-icons/heroicons-outline"; +import ComboBox from "../components/ComboBox"; let TopGUIDiv = styled.div` display: grid; grid-template-columns: 1fr; @@ -42,128 +43,132 @@ interface GUIProps { function GUI(props: GUIProps) { const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); - const [history, setHistory] = useState({ - timeline: [ - { - step: { - name: "Waiting for user input", - cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", - }, - observation: { - title: "ERROR FOUND", - error: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - output: [ - { - traceback: { - frames: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - lineno: 7, - function: "", - code: "print(sum(first, second))", - }, - ], - message: "unsupported operand type(s) for +: 'int' and 'str'", - error_type: - ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - full_traceback: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - }, - null, - ], - }, - { - step: { - name: "EditCodeStep", - range_in_files: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - range: { - start: { - line: 0, - character: 0, - }, - end: { - line: 6, - character: 25, - }, - }, - }, - ], - prompt: - "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", - }, - output: [ - null, - { - reversible: true, - actions: [ - { - reversible: true, - filesystem: {}, - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - range: { - start: { - line: 0, - character: 0, - }, - end: { - line: 6, - character: 25, - }, - }, - replacement: - "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", - }, - ], - }, - ], - }, - { - step: { - name: "SolveTracebackStep", - traceback: { - frames: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - lineno: 7, - function: "", - code: "print(sum(first, second))", - }, - ], - message: "unsupported operand type(s) for +: 'int' and 'str'", - error_type: - ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - full_traceback: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - description: "Running step: SolveTracebackStep", - }, - output: [null, null], - }, - { - step: { - name: "RunCodeStep", - cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - }, - output: [null, null], - }, - ], - current_index: 3, - } as any); + const [availableSlashCommands, setAvailableSlashCommands] = useState< + { name: string; description: string }[] + >([]); + const [history, setHistory] = useState(); + // { + // timeline: [ + // { + // step: { + // name: "Waiting for user input", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", + // }, + // observation: { + // title: "ERROR FOUND", + // error: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // output: [ + // { + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // }, + // null, + // ], + // }, + // { + // step: { + // name: "EditCodeStep", + // range_in_files: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // }, + // ], + // prompt: + // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", + // }, + // output: [ + // null, + // { + // reversible: true, + // actions: [ + // { + // reversible: true, + // filesystem: {}, + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // replacement: + // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", + // }, + // ], + // }, + // ], + // }, + // { + // step: { + // name: "SolveTracebackStep", + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // description: "Running step: SolveTracebackStep", + // }, + // output: [null, null], + // }, + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [null, null], + // }, + // ], + // current_index: 3, + // } as any); const topGuiDivRef = useRef(null); const client = useContinueGUIProtocol(); @@ -197,13 +202,24 @@ function GUI(props: GUIProps) { scrollToBottom(); }); + client?.onAvailableSlashCommands((commands) => { + console.log("Received available slash commands: ", commands); + setAvailableSlashCommands( + commands.map((c) => { + return { + name: "/" + c.name, + description: c.description, + }; + }) + ); + }); }, [client]); useEffect(() => { scrollToBottom(); }, [waitingForSteps]); - const mainTextInputRef = useRef(null); + const mainTextInputRef = useRef(null); useEffect(() => { if (mainTextInputRef.current) { @@ -246,8 +262,6 @@ function GUI(props: GUIProps) { return [...queue, input]; }); } - mainTextInputRef.current.value = ""; - mainTextInputRef.current.style.height = ""; } setWaitingForSteps(true); @@ -270,7 +284,12 @@ function GUI(props: GUIProps) { }} > -

Continue

+
+ + Continue Docs + + + Clear History - { - if (e.key === "Enter") { - onMainTextInput(); - e.stopPropagation(); - e.preventDefault(); - } - }} - rows={1} - onChange={() => { - const textarea = mainTextInputRef.current!; - textarea.style.height = ""; /* Reset the height*/ - textarea.style.height = `${Math.min( - textarea.scrollHeight - 15, - 500 - )}px`; + onEnter={(e) => { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); }} + onInputValueChange={() => {}} + items={availableSlashCommands} /> diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 42f3d4a3..3d7639a9 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From 01cfbc179a33c99d55acdc989dbafd554db16a92 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 12 Jun 2023 21:34:05 -0700 Subject: clear history button fix --- extension/react-app/src/tabs/gui.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 1569c178..a3a48410 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -290,14 +290,14 @@ function GUI(props: GUIProps) { - + { + client?.sendClear(); + }} + style={{ padding: "3px" }} + > Clear History - { - client?.sendClear(); - }} - /> + -- cgit v1.2.3-70-g09d2 From 0a7ca710ae4af16933ef128d13c050b99912e71a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 12 Jun 2023 22:14:46 -0700 Subject: added slash commands, patched --- continuedev/src/continuedev/core/config.py | 10 ++++++++++ continuedev/src/continuedev/core/policy.py | 12 ++++-------- continuedev/src/continuedev/libs/util/step_name_to_steps.py | 6 +++++- .../src/continuedev/recipes/WritePytestsRecipe/main.py | 4 ++-- continuedev/src/continuedev/steps/comment_code.py | 4 ++-- extension/react-app/src/tabs/gui.tsx | 6 +++--- 6 files changed, 26 insertions(+), 16 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index cf723984..ed056be9 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -43,6 +43,16 @@ class ContinueConfig(BaseModel): description="Deploy a dlt pipeline to Airflow", step_name="DeployPipelineAirflowRecipe", ), + SlashCommand( + name="comment", + description="Write comments for the current file or highlighted code", + step_name="CommentCodeStep", + ), + SlashCommand( + name="pytest", + description="Write pytest unit tests for the current file", + step_name="WritePytestsRecipe", + ) ] diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 37a10e36..51135f70 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -41,15 +41,11 @@ class DemoPolicy(Policy): after_command = " ".join(user_input.split(" ")[1:]) for slash_command in config.slash_commands: if slash_command.name == command_name[1:]: - return get_step_from_name(slash_command.step_name, slash_command.params) + params = slash_command.params + params["user_input"] = after_command + return get_step_from_name(slash_command.step_name, params) - if "/pytest" in user_input.lower(): - return WritePytestsRecipe(instructions=user_input) - if "/pytest" in observation.user_input.lower(): - return WritePytestsRecipe(instructions=observation.user_input) - elif "/comment" in observation.user_input.lower(): - return CommentCodeStep() - elif "/ask" in user_input: + if "/ask" in user_input: return AnswerQuestionChroma(question=" ".join(user_input.split(" ")[1:])) elif "/edit" in user_input: return EditFileChroma(request=" ".join(user_input.split(" ")[1:])) diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py index 4023b73b..fe784964 100644 --- a/continuedev/src/continuedev/libs/util/step_name_to_steps.py +++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py @@ -7,6 +7,8 @@ from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe from ...recipes.AddTransformRecipe.main import AddTransformRecipe +from ...recipes.WritePytestsRecipe.main import WritePytestsRecipe +from ...steps.comment_code import CommentCodeStep step_name_to_step_class = { "UserInputStep": UserInputStep, @@ -14,7 +16,9 @@ step_name_to_step_class = { "DDtoBQRecipe": DDtoBQRecipe, "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe, "AddTransformRecipe": AddTransformRecipe, - "DDtoBQRecipe": DDtoBQRecipe + "DDtoBQRecipe": DDtoBQRecipe, + "WritePytestsRecipe": WritePytestsRecipe, + "CommentCodeStep": CommentCodeStep, } diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py index 5994aa89..a19caf8b 100644 --- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py @@ -7,7 +7,7 @@ import os class WritePytestsRecipe(Step): for_filepath: Union[str, None] = None - instructions: str = "Write unit tests for this file." + user_input: str = "Write unit tests for this file." async def run(self, sdk: ContinueSDK): if self.for_filepath is None: @@ -35,7 +35,7 @@ class WritePytestsRecipe(Step): Here are additional instructions: - "{self.instructions}" + "{self.user_input}" Here is a complete set of pytest unit tests:""") tests = sdk.models.gpt35.complete(prompt) diff --git a/continuedev/src/continuedev/steps/comment_code.py b/continuedev/src/continuedev/steps/comment_code.py index c538347b..aa17e62c 100644 --- a/continuedev/src/continuedev/steps/comment_code.py +++ b/continuedev/src/continuedev/steps/comment_code.py @@ -1,5 +1,5 @@ from ..core.main import ContinueSDK, Models, Step -from .main import StarCoderEditHighlightedCodeStep +from .main import EditHighlightedCodeStep class CommentCodeStep(Step): @@ -9,4 +9,4 @@ class CommentCodeStep(Step): return "Writing comments" async def run(self, sdk: ContinueSDK): - await sdk.run_step(StarCoderEditHighlightedCodeStep(user_input="Wrote comprehensive comments in the canonical format for every class and function")) + await sdk.run_step(EditHighlightedCodeStep(user_input="Write comprehensive comments in the canonical format for every class and function")) diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index a3a48410..bf6a9401 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -242,14 +242,14 @@ function GUI(props: GUIProps) { let input = mainTextInputRef.current.value; if ( - history && + history?.timeline.length && history.timeline[history.current_index].step.name === "Waiting for user input" ) { if (input.trim() === "") return; onStepUserInput(input, history!.current_index); } else if ( - history && + history?.timeline.length && history.timeline[history.current_index].step.name === "Waiting for user confirmation" ) { @@ -344,7 +344,7 @@ function GUI(props: GUIProps) { Date: Tue, 13 Jun 2023 12:00:35 -0700 Subject: env to .env to be caught by .gitignore --- continuedev/src/continuedev/core/policy.py | 2 +- .../continuedev/recipes/AddTransformRecipe/steps.py | 8 ++++---- .../recipes/CreatePipelineRecipe/steps.py | 10 +++++----- .../src/continuedev/recipes/DDtoBQRecipe/steps.py | 10 +++++----- .../recipes/DeployPipelineAirflowRecipe/steps.py | 8 ++++---- .../continuedev/recipes/WritePytestsRecipe/main.py | 3 +++ docs/docs/walkthroughs/create-a-recipe.md | 2 +- extension/react-app/src/components/ComboBox.tsx | 2 +- extension/react-app/src/components/StepContainer.tsx | 20 +++++++++++--------- 9 files changed, 35 insertions(+), 30 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 51135f70..2b50307a 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -58,7 +58,7 @@ class DemoPolicy(Policy): # AnswerQuestionChroma(question=user_input), # EditFileChroma(request=user_input), (SimpleChatStep(user_input=user_input), - "Respond to the user with a chat message"), + "Respond to the user with a chat message. Can answer questions about code or anything else."), ], default_step=EditHighlightedCodeStep(user_input=user_input)) state = history.get_current() diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py index f042424c..6a743fb5 100644 --- a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py @@ -24,15 +24,15 @@ class SetUpChessPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', 'dlt --non-interactive init chess duckdb', 'pip install -r requirements.txt', 'pip install pandas streamlit' # Needed for the pipeline show step later ], name="Set up Python environment", description=dedent(f"""\ - - Create a Python virtual environment: `python3 -m venv env` - - Activate the virtual environment: `source env/bin/activate` + - Create a Python virtual environment: `python3 -m venv .env` + - Activate the virtual environment: `source .env/bin/activate` - Install dlt: `pip install dlt` - Create a new dlt pipeline called "chess" that loads data into a local DuckDB instance: `dlt init chess duckdb` - Install the Python dependencies for the pipeline: `pip install -r requirements.txt`""")) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index ea4607da..88e27d2a 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -35,15 +35,15 @@ class SetupPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', f'dlt --non-interactive init {source_name} duckdb', 'pip install -r requirements.txt' ], description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init {source_name} duckdb`: Create a new dlt pipeline called {source_name} that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""), name="Setup Python environment") @@ -156,7 +156,7 @@ class RunQueryStep(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - output = await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected", handle_error=False) + output = await sdk.run('.env/bin/python3 query.py', name="Run test query", description="Running `.env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected", handle_error=False) if "Traceback" in output or "SyntaxError" in output: suggestion = sdk.models.gpt35.complete(dedent(f"""\ diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py index 5cf89ccf..4b8971c2 100644 --- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py @@ -27,15 +27,15 @@ class SetUpChessPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', 'dlt --non-interactive init chess duckdb', 'pip install -r requirements.txt', ], name="Set up Python environment", description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init chess duckdb`: Create a new dlt pipeline called "chess" that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline""")) @@ -75,7 +75,7 @@ class LoadDataStep(Step): async def run(self, sdk: ContinueSDK): # Run the pipeline again to load data to BigQuery - output = await sdk.run('env/bin/python3 chess_pipeline.py', name="Load data to BigQuery", description="Running `env/bin/python3 chess_pipeline.py` to load data to Google BigQuery") + output = await sdk.run('.env/bin/python3 chess_pipeline.py', name="Load data to BigQuery", description="Running `.env/bin/python3 chess_pipeline.py` to load data to Google BigQuery") if "Traceback" in output or "SyntaxError" in output: with open(os.path.join(os.path.dirname(__file__), "dlt_duckdb_to_bigquery_docs.md"), "r") as f: diff --git a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py b/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py index ee3275e7..c9749348 100644 --- a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py @@ -28,15 +28,15 @@ class SetupPipelineStep(Step): async def run(self, sdk: ContinueSDK): await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', f'dlt --non-interactive init {self.source_name} duckdb', 'pip install -r requirements.txt' ], description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init {self.source_name} duckdb`: Create a new dlt pipeline called {self.source_name} that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""), name="Setup Python environment") diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py index a19caf8b..688f44c3 100644 --- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py @@ -9,6 +9,9 @@ class WritePytestsRecipe(Step): for_filepath: Union[str, None] = None user_input: str = "Write unit tests for this file." + async def describe(self, models): + return f"Writing unit tests for {self.for_filepath}" + async def run(self, sdk: ContinueSDK): if self.for_filepath is None: self.for_filepath = (await sdk.ide.getOpenFiles())[0] diff --git a/docs/docs/walkthroughs/create-a-recipe.md b/docs/docs/walkthroughs/create-a-recipe.md index 3b80df8a..5d80d083 100644 --- a/docs/docs/walkthroughs/create-a-recipe.md +++ b/docs/docs/walkthroughs/create-a-recipe.md @@ -93,5 +93,5 @@ class SetUpVenvStep(Step): if os == "Windows": await sdk.run("python -m venv env; .\\env\\Scripts\\activate") else: - await sdk.run("python3 -m venv env && source env/bin/activate") # MacOS and Linux + await sdk.run("python3 -m venv .env && source .env/bin/activate") # MacOS and Linux ``` diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 1b7c60e6..46d8c4d3 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -105,7 +105,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
) : ( - { - return ( - - ); - }, - code: ({ node, ...props }) => { - return ; - }, - ul: ({ node, ...props }) => { - return
    ; - }, - }} - > - {props.historyNode.step.description as any} - + )} -- cgit v1.2.3-70-g09d2 From 5c6d2f1be8474d26124506e0c2a640fa68efe52d Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 02:30:45 -0700 Subject: fixed unique_id being asyncio.run property --- continuedev/src/continuedev/server/ide.py | 22 +++++++++++----------- continuedev/src/continuedev/server/ide_protocol.py | 5 +---- continuedev/src/continuedev/steps/help.py | 8 +++++--- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/react-app/src/components/ComboBox.tsx | 17 ++--------------- extension/react-app/src/components/Onboarding.tsx | 1 + 7 files changed, 23 insertions(+), 36 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 12a21f19..73cce201 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -123,10 +123,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self.websocket = websocket self.session_manager = session_manager - workspace_directory: str + workspace_directory: str = None + unique_id: str = None async def initialize(self) -> List[str]: await self._send_json("workspaceDirectory", {}) + await self._send_json("uniqueId", {}) other_msgs = [] while True: msg_string = await self.websocket.receive_text() @@ -137,9 +139,13 @@ class IdeProtocolServer(AbstractIdeProtocolServer): data = message["data"] if message_type == "workspaceDirectory": self.workspace_directory = data["workspaceDirectory"] - break + elif message_type == "uniqueId": + self.unique_id = data["uniqueId"] else: other_msgs.append(msg_string) + + if self.workspace_directory is not None and self.unique_id is not None: + break return other_msgs async def _send_json(self, message_type: str, data: Any): @@ -183,10 +189,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self.onMainUserInput(data["input"]) elif message_type == "deleteAtIndex": self.onDeleteAtIndex(data["index"]) - elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand", "uniqueId"]: + elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand"]: self.sub_queue.post(message_type, data) elif message_type == "workspaceDirectory": self.workspace_directory = data["workspaceDirectory"] + elif message_type == "uniqueId": + self.unique_id = data["uniqueId"] else: raise ValueError("Unknown message type", message_type) @@ -311,14 +319,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer): resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles") return resp.visibleFiles - async def get_unique_id(self) -> str: - resp = await self._send_and_receive_json({}, UniqueIdResponse, "uniqueId") - return resp.uniqueId - - @cached_property_no_none - def unique_id(self) -> str: - return asyncio.run(self.get_unique_id()) - async def getHighlightedCode(self) -> List[RangeInFile]: resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode") return resp.highlightedCode diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 2f78cf0e..d0fb0bf8 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -108,7 +108,4 @@ class AbstractIdeProtocolServer(ABC): """Show a diff""" workspace_directory: str - - @abstractproperty - def unique_id(self) -> str: - """Get a unique ID for this IDE""" + unique_id: str diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py index fdfb986f..2dc3647c 100644 --- a/continuedev/src/continuedev/steps/help.py +++ b/continuedev/src/continuedev/steps/help.py @@ -6,7 +6,7 @@ from ..libs.util.telemetry import capture_event help = dedent("""\ Continue is an open-source coding autopilot. It is a VS Code extension that brings the power of ChatGPT to your IDE. - It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized LLM later. + It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized Large Language Model (LLM) later. Continue can be used to... 1. Edit chunks of code with specific instructions (e.g. "/edit migrate this digital ocean terraform file into one that works for GCP") @@ -25,6 +25,7 @@ help = dedent("""\ If you have feedback, please use /feedback to let us know how you would like to use Continue. We are excited to hear from you!""") + class HelpStep(Step): name: str = "Help" @@ -41,7 +42,7 @@ class HelpStep(Step): Information: {help}""") - + self.chat_context.append(ChatMessage( role="user", content=prompt, @@ -54,4 +55,5 @@ class HelpStep(Step): self.description += chunk["content"] await sdk.update_ui() - capture_event(sdk.ide.unique_id, "help", {"question": question, "answer": self.description}) \ No newline at end of file + capture_event(sdk.ide.unique_id, "help", { + "question": question, "answer": self.description}) diff --git a/extension/package-lock.json b/extension/package-lock.json index 65fdab12..9d5c73e1 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.162", + "version": "0.0.163", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.162", + "version": "0.0.163", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index ef39582b..2b0f6b94 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.162", + "version": "0.0.163", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 7d6541c7..73db33ca 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,29 +1,16 @@ -import React, { - useCallback, - useEffect, - useImperativeHandle, - useState, -} from "react"; +import React, { useEffect, useImperativeHandle, useState } from "react"; import { useCombobox } from "downshift"; import styled from "styled-components"; import { - buttonColor, defaultBorderRadius, lightGray, secondaryDark, vscBackground, } from "."; import CodeBlock from "./CodeBlock"; -import { RangeInFile } from "../../../src/client"; import PillButton from "./PillButton"; import HeaderButtonWithText from "./HeaderButtonWithText"; -import { - Trash, - LockClosed, - LockOpen, - Plus, - DocumentPlus, -} from "@styled-icons/heroicons-outline"; +import { DocumentPlus } from "@styled-icons/heroicons-outline"; import { HighlightedRangeContext } from "../../../schema/FullState"; // #region styled components diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index e2dd6f57..6bfb0ccd 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -22,6 +22,7 @@ const StyledSpan = styled.span` &:hover { background-color: #ffffff33; } + white-space: nowrap; `; const Onboarding = () => { -- cgit v1.2.3-70-g09d2 From 39cd2ef27d6ed439b00a9edec4a487343ff1c2c9 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 03:24:46 -0700 Subject: warn of large highlighted ranges, cmd+k->m --- continuedev/src/continuedev/core/policy.py | 2 +- continuedev/src/continuedev/steps/help.py | 2 +- extension/package-lock.json | 4 +- extension/package.json | 6 +- extension/react-app/src/components/ComboBox.tsx | 5 + extension/react-app/src/components/Onboarding.tsx | 1 + extension/react-app/src/components/PillButton.tsx | 167 +++++++++++++--------- extension/react-app/src/pages/gui.tsx | 27 ++-- extension/src/commands.ts | 17 ++- extension/src/lang-server/codeLens.ts | 4 +- 10 files changed, 141 insertions(+), 94 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 59ea78b1..bc897357 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -59,7 +59,7 @@ class DemoPolicy(Policy): return ( MessageStep(name="Welcome to Continue", message=dedent("""\ - Highlight code and ask a question or give instructions - - Use `cmd+k` (Mac) / `ctrl+k` (Windows) to open Continue + - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue - Use `/help` to ask questions about how to use Continue""")) >> WelcomeStep() >> # SetupContinueWorkspaceStep() >> diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py index 2dc3647c..ba1e6087 100644 --- a/continuedev/src/continuedev/steps/help.py +++ b/continuedev/src/continuedev/steps/help.py @@ -19,7 +19,7 @@ help = dedent("""\ Continue passes all of the sections of code you highlight, the code above and below the to-be edited highlighted code section, and all previous steps above input box as context to the LLM. - You can use cmd+k (Mac) / ctrl+k (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,` + You can use cmd+m (Mac) / ctrl+m (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,` If Continue is stuck loading, try using `cmd+shift+p` to open the command palette, search "Reload Window", and then select it. This will reload VS Code and Continue and often fixes issues. diff --git a/extension/package-lock.json b/extension/package-lock.json index 9d5c73e1..a79dd6b4 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.163", + "version": "0.0.164", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.163", + "version": "0.0.164", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 2b0f6b94..de1f395d 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.163", + "version": "0.0.164", "publisher": "Continue", "engines": { "vscode": "^1.67.0" @@ -111,8 +111,8 @@ "keybindings": [ { "command": "continue.focusContinueInput", - "mac": "cmd+k", - "key": "ctrl+k" + "mac": "cmd+m", + "key": "ctrl+m" }, { "command": "continue.suggestionDown", diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 73db33ca..bd0d59b5 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -228,6 +228,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { )} */} {highlightedCodeSections.map((section, idx) => ( 4000 && section.editing + ? "Editing such a large range may be slow" + : undefined + } editing={section.editing} pinned={section.pinned} index={idx} diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index 6bfb0ccd..231c1e93 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -109,6 +109,7 @@ const Onboarding = () => { paddingBottom: "50px", textAlign: "center", cursor: "pointer", + whiteSpace: "nowrap", }} > ` } `; +const CircleDiv = styled.div` + position: absolute; + top: -10px; + right: -10px; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: red; + color: white; + display: flex; + align-items: center; + justify-content: center; + padding: 2px; +`; + interface PillButtonProps { onHover?: (arg0: boolean) => void; onDelete?: () => void; @@ -55,6 +68,7 @@ interface PillButtonProps { index: number; editing: boolean; pinned: boolean; + warning?: string; } const PillButton = (props: PillButtonProps) => { @@ -63,75 +77,96 @@ const PillButton = (props: PillButtonProps) => { return ( <> - + + {props.editing + ? "Editing this range (with rest of file as context)" + : "Edit this range"} + + Delete + {props.warning && ( + <> + + + + + {props.warning} - { - if (props.onDelete) { - props.onDelete(); - } - }} - > - - - + )} - {props.title} - - - {props.editing - ? "Editing this range (with rest of file as context)" - : "Edit this range"} - - Delete +
); }; diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 4ff260fa..57cebac3 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -95,11 +95,8 @@ function GUI(props: GUIProps) { name: "Welcome to Continue", hide: false, description: `- Highlight code and ask a question or give instructions -- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue -- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer -- Add your own OpenAI API key to VS Code Settings with \`cmd+,\` -- Use slash commands when you want fine-grained control -- Past steps are included as part of the context by default`, + - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue + - Use \`/help\` to ask questions about how to use Continue`, system_message: null, chat_context: [], manage_own_chat_context: false, @@ -269,15 +266,17 @@ function GUI(props: GUIProps) { return ( <> - { - client?.sendMainInput(`/feedback ${text}`); - setShowFeedbackDialog(false); - }} - onClose={() => { - setShowFeedbackDialog(false); - }} - message={feedbackDialogMessage} /> + { + client?.sendMainInput(`/feedback ${text}`); + setShowFeedbackDialog(false); + }} + onClose={() => { + setShowFeedbackDialog(false); + }} + message={feedbackDialogMessage} + /> any } = { "continue.suggestionDown": suggestionDownCommand, @@ -30,10 +32,15 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptAllSuggestions": acceptAllSuggestionsCommand, "continue.rejectAllSuggestions": rejectAllSuggestionsCommand, "continue.focusContinueInput": async () => { - vscode.commands.executeCommand("continue.continueGUIView.focus"); - debugPanelWebview?.postMessage({ - type: "focusContinueInput", - }); + if (focusedOnContinueInput) { + vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup"); + } else { + vscode.commands.executeCommand("continue.continueGUIView.focus"); + debugPanelWebview?.postMessage({ + type: "focusContinueInput", + }); + } + focusedOnContinueInput = !focusedOnContinueInput; }, "continue.quickTextEntry": async () => { const text = await vscode.window.showInputBox({ @@ -53,4 +60,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) { vscode.commands.registerCommand(command, callback) ); } -} \ No newline at end of file +} diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts index 5800a00e..1cfef5d5 100644 --- a/extension/src/lang-server/codeLens.ts +++ b/extension/src/lang-server/codeLens.ts @@ -60,12 +60,12 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider { } codeLenses.push( new vscode.CodeLens(range, { - title: "Accept ✅ (⌘⇧↩)", + title: "Accept All ✅ (⌘⇧↩)", command: "continue.acceptDiff", arguments: [document.uri.fsPath], }), new vscode.CodeLens(range, { - title: "Reject ❌ (⌘⇧⌫)", + title: "Reject All ❌ (⌘⇧⌫)", command: "continue.rejectDiff", arguments: [document.uri.fsPath], }) -- cgit v1.2.3-70-g09d2 From b19076ddb6d11acb5ffd54046d9e5cad549c00c1 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 11:01:06 -0700 Subject: command m reliable toggle --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/react-app/src/components/ComboBox.tsx | 4 ++++ extension/src/commands.ts | 5 ++++- extension/src/debugPanel.ts | 5 +++++ extension/src/diffs.ts | 21 ++++++++++++--------- 6 files changed, 28 insertions(+), 13 deletions(-) (limited to 'extension/react-app') diff --git a/extension/package-lock.json b/extension/package-lock.json index a79dd6b4..12aa27c9 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.164", + "version": "0.0.165", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.164", + "version": "0.0.165", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index de1f395d..05bd4d84 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.164", + "version": "0.0.165", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index bd0d59b5..5d9b5109 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -12,6 +12,7 @@ import PillButton from "./PillButton"; import HeaderButtonWithText from "./HeaderButtonWithText"; import { DocumentPlus } from "@styled-icons/heroicons-outline"; import { HighlightedRangeContext } from "../../../schema/FullState"; +import { postVscMessage } from "../vscode"; // #region styled components const mainInputFontSize = 13; @@ -297,6 +298,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { // setShowContextDropdown(target.value.endsWith("@")); }, + onBlur: (e) => { + postVscMessage("blurContinueInput", {}); + }, onKeyDown: (event) => { if (event.key === "Enter" && event.shiftKey) { // Prevent Downshift's default 'Enter' behavior. diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 0025340a..888f01ed 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -16,11 +16,14 @@ import { import { acceptDiffCommand, rejectDiffCommand } from "./diffs"; import * as bridge from "./bridge"; import { debugPanelWebview } from "./debugPanel"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { ideProtocolClient } from "./activation/activate"; let focusedOnContinueInput = false; +export const setFocusedOnContinueInput = (value: boolean) => { + focusedOnContinueInput = value; +}; + // Copy everything over from extension.ts const commandsMap: { [command: string]: (...args: any) => any } = { "continue.suggestionDown": suggestionDownCommand, diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 5e1689d1..dd24a8d8 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -6,6 +6,7 @@ import { openEditorAndRevealRange, } from "./util/vscode"; import { RangeInFile } from "./client"; +import { setFocusedOnContinueInput } from "./commands"; const WebSocket = require("ws"); let websocketConnections: { [url: string]: WebsocketConnection | undefined } = @@ -226,6 +227,10 @@ export function setupDebugPanel( openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One); break; } + case "blurContinueInput": { + setFocusedOnContinueInput(false); + break; + } case "withProgress": { // This message allows withProgress to be used in the webview if (data.done) { diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 910c30f2..37943de4 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -104,6 +104,17 @@ class DiffManager { return editor; } + private _findFirstDifferentLine(contentA: string, contentB: string): number { + const linesA = contentA.split("\n"); + const linesB = contentB.split("\n"); + for (let i = 0; i < linesA.length && i < linesB.length; i++) { + if (linesA[i] !== linesB[i]) { + return i; + } + } + return 0; + } + writeDiff( originalFilepath: string, newContent: string, @@ -119,15 +130,7 @@ class DiffManager { if (!this.diffs.has(newFilepath)) { // Figure out the first line that is different const oldContent = fs.readFileSync(originalFilepath).toString("utf-8"); - let line = 0; - const newLines = newContent.split("\n"); - const oldLines = oldContent.split("\n"); - for (let i = 0; i < newLines.length && i < oldLines.length; i++) { - if (newLines[i] !== oldLines[i]) { - line = i; - break; - } - } + const line = this._findFirstDifferentLine(oldContent, newContent); const diffInfo: DiffInfo = { originalFilepath, -- cgit v1.2.3-70-g09d2 From c5102b0997baa81ce544514d6b5b4d5a2eae804f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 13:45:10 -0700 Subject: insidious client_state vs application_state err --- continuedev/src/continuedev/core/autopilot.py | 2 ++ continuedev/src/continuedev/server/gui.py | 2 +- continuedev/src/continuedev/server/ide.py | 2 +- continuedev/src/continuedev/steps/core/core.py | 9 ++++++++- extension/react-app/src/components/ComboBox.tsx | 9 ++++++++- extension/react-app/src/components/StepContainer.tsx | 9 ++++++--- extension/src/continueIdeClient.ts | 8 ++++++-- extension/src/diffs.ts | 2 +- extension/src/util/messenger.ts | 2 +- 9 files changed, 34 insertions(+), 11 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index e1c8a076..82439f49 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -37,6 +37,8 @@ def get_error_title(e: Exception) -> str: return "The request failed. Please check your internet connection and try again. If this issue persists, you can use our API key for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to \"\"" elif isinstance(e, openai_errors.InvalidRequestError): return 'Your API key does not have access to GPT-4. You can use ours for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to ""' + elif e.__str__().startswith("Cannot connect to host"): + return "The request failed. Please check your internet connection and try again." return e.__str__() or e.__repr__() diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 238273b2..9a411fbe 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -53,7 +53,7 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.session = session async def _send_json(self, message_type: str, data: Any): - if self.websocket.client_state == WebSocketState.DISCONNECTED: + if self.websocket.application_state == WebSocketState.DISCONNECTED: return await self.websocket.send_json({ "messageType": message_type, diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 73cce201..7875c94d 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -149,7 +149,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): return other_msgs async def _send_json(self, message_type: str, data: Any): - if self.websocket.client_state == WebSocketState.DISCONNECTED: + if self.websocket.application_state == WebSocketState.DISCONNECTED: return await self.websocket.send_json({ "messageType": message_type, diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 787da316..75f8e460 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -9,7 +9,7 @@ from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ...core.main import ChatMessage, Step, SequentialStep +from ...core.main import ChatMessage, ContinueCustomException, Step, SequentialStep from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS from ...libs.util.dedent import dedent_and_get_common_whitespace import difflib @@ -608,6 +608,13 @@ Please output the code to be inserted at the cursor in order to fulfill the user rif_dict[rif.filepath] = rif.contents for rif in rif_with_contents: + # If the file doesn't exist, ask them to save it first + if not os.path.exists(rif.filepath): + message = f"The file {rif.filepath} does not exist. Please save it first." + raise ContinueCustomException( + title=message, message=message + ) + await sdk.ide.setFileOpen(rif.filepath) await sdk.ide.setSuggestionsLocked(rif.filepath, True) await self.stream_rif(rif, sdk) diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 5d9b5109..754c9445 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -169,6 +169,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useImperativeHandle(ref, () => downshiftProps, [downshiftProps]); const [metaKeyPressed, setMetaKeyPressed] = useState(false); + const [focused, setFocused] = useState(false); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Meta") { @@ -298,7 +299,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { // setShowContextDropdown(target.value.endsWith("@")); }, + onFocus: (e) => { + setFocused(true); + }, onBlur: (e) => { + setFocused(false); postVscMessage("blurContinueInput", {}); }, onKeyDown: (event) => { @@ -374,7 +379,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
{highlightedCodeSections.length === 0 && (downshiftProps.inputValue?.startsWith("/edit") || - (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && ( + (focused && + metaKeyPressed && + downshiftProps.inputValue?.length > 0)) && (
Inserting at cursor
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 6fa4ba13..14e9b854 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -253,9 +253,12 @@ function StepContainer(props: StepContainerProps) { )} {props.historyNode.observation?.error ? ( -
-              {props.historyNode.observation.error as string}
-            
+
+ View Traceback +
+                {props.historyNode.observation.error as string}
+              
+
) : ( void): void; abstract onClose(callback: () => void): void; - + abstract onError(callback: () => void): void; abstract sendAndReceive(messageType: string, data: any): Promise; -- cgit v1.2.3-70-g09d2 From 48e5c8001e897eb37493357087410ee8f98217fa Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 15 Jul 2023 14:30:11 -0700 Subject: ctrl shortcuts on windows, load models immediately --- continuedev/src/continuedev/core/autopilot.py | 10 ++-- continuedev/src/continuedev/core/sdk.py | 59 +++++++++++++++------- .../src/continuedev/libs/llm/hf_inference_api.py | 6 ++- continuedev/src/continuedev/server/ide.py | 4 +- .../src/continuedev/server/session_manager.py | 6 +-- .../react-app/src/components/StepContainer.tsx | 2 +- extension/react-app/src/components/TextDialog.tsx | 6 ++- extension/react-app/src/pages/gui.tsx | 6 +-- extension/react-app/src/util/index.ts | 30 +++++++++++ 9 files changed, 98 insertions(+), 31 deletions(-) create mode 100644 extension/react-app/src/util/index.ts (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 82439f49..0696c360 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -50,6 +50,8 @@ class Autopilot(ContinueBaseModel): full_state: Union[FullState, None] = None _on_update_callbacks: List[Callable[[FullState], None]] = [] + continue_sdk: ContinueSDK = None + _active: bool = False _should_halt: bool = False _main_user_input_queue: List[str] = [] @@ -57,9 +59,11 @@ class Autopilot(ContinueBaseModel): _user_input_queue = AsyncSubscriptionQueue() _retry_queue = AsyncSubscriptionQueue() - @cached_property - def continue_sdk(self) -> ContinueSDK: - return ContinueSDK(self) + @classmethod + async def create(cls, policy: Policy, ide: AbstractIdeProtocolServer, full_state: FullState) -> "Autopilot": + autopilot = cls(ide=ide, policy=policy) + autopilot.continue_sdk = await ContinueSDK.create(autopilot) + return autopilot class Config: arbitrary_types_allowed = True diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index aa2d8892..d73561d2 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,6 +1,6 @@ import asyncio from functools import cached_property -from typing import Coroutine, Union +from typing import Coroutine, Dict, Union import os from ..steps.core.core import DefaultModelEditCodeStep @@ -13,7 +13,7 @@ from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer -from .main import Context, ContinueCustomException, HighlightedRangeContext, History, Step, ChatMessage, ChatMessageRole +from .main import Context, ContinueCustomException, History, Step, ChatMessage from ..steps.core.core import * from ..libs.llm.proxy_server import ProxyServer @@ -22,26 +22,46 @@ class Autopilot: pass +ModelProvider = Literal["openai", "hf_inference_api", "ggml", "anthropic"] +MODEL_PROVIDER_TO_ENV_VAR = { + "openai": "OPENAI_API_KEY", + "hf_inference_api": "HUGGING_FACE_TOKEN", + "anthropic": "ANTHROPIC_API_KEY" +} + + class Models: - def __init__(self, sdk: "ContinueSDK"): + provider_keys: Dict[ModelProvider, str] = {} + model_providers: List[ModelProvider] + + def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]): self.sdk = sdk + self.model_providers = model_providers + + @classmethod + async def create(cls, sdk: "ContinueSDK", with_providers: List[ModelProvider] = ["openai"]) -> "Models": + models = Models(sdk, with_providers) + for provider in with_providers: + if provider in MODEL_PROVIDER_TO_ENV_VAR: + env_var = MODEL_PROVIDER_TO_ENV_VAR[provider] + models.provider_keys[provider] = await sdk.get_user_secret( + env_var, f'Please add your {env_var} to the .env file') + + return models def __load_openai_model(self, model: str) -> OpenAI: - async def load_openai_model(): - api_key = await self.sdk.get_user_secret( - 'OPENAI_API_KEY', 'Enter your OpenAI API key or press enter to try for free') - if api_key == "": - return ProxyServer(self.sdk.ide.unique_id, model) - return OpenAI(api_key=api_key, default_model=model) - return asyncio.get_event_loop().run_until_complete(load_openai_model()) + api_key = self.provider_keys["openai"] + if api_key == "": + return ProxyServer(self.sdk.ide.unique_id, model) + return OpenAI(api_key=api_key, default_model=model) + + def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI: + api_key = self.provider_keys["hf_inference_api"] + return HuggingFaceInferenceAPI(api_key=api_key, model=model) @cached_property def starcoder(self): - async def load_starcoder(): - api_key = await self.sdk.get_user_secret( - 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file') - return HuggingFaceInferenceAPI(api_key=api_key) - return asyncio.get_event_loop().run_until_complete(load_starcoder()) + return self.__load_hf_inference_api_model("bigcode/starcoder") @cached_property def gpt35(self): @@ -74,7 +94,7 @@ class Models: @property def default(self): default_model = self.sdk.config.default_model - return self.__model_from_name(default_model) if default_model is not None else self.gpt35 + return self.__model_from_name(default_model) if default_model is not None else self.gpt4 class ContinueSDK(AbstractContinueSDK): @@ -87,10 +107,15 @@ class ContinueSDK(AbstractContinueSDK): def __init__(self, autopilot: Autopilot): self.ide = autopilot.ide self.__autopilot = autopilot - self.models = Models(self) self.context = autopilot.context self.config = self._load_config() + @classmethod + async def create(cls, autopilot: Autopilot) -> "ContinueSDK": + sdk = ContinueSDK(autopilot) + sdk.models = await Models.create(sdk) + return sdk + config: ContinueConfig def _load_config(self) -> ContinueConfig: diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py index 1586c620..803ba122 100644 --- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -9,7 +9,11 @@ DEFAULT_MAX_TIME = 120. class HuggingFaceInferenceAPI(LLM): api_key: str - model: str = "bigcode/starcoder" + model: str + + def __init__(self, api_key: str, model: str): + self.api_key = api_key + self.model = model def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs): """Return the completion of the text with the given temperature.""" diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 7875c94d..77b13483 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -227,8 +227,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): }) async def getSessionId(self): - session_id = self.session_manager.new_session( - self, self.session_id).session_id + session_id = (await self.session_manager.new_session( + self, self.session_id)).session_id await self._send_json("getSessionId", { "sessionId": session_id }) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index fb8ac386..6d109ca6 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -53,18 +53,18 @@ class SessionManager: session_files = os.listdir(sessions_folder) if f"{session_id}.json" in session_files and session_id in self.registered_ides: if self.registered_ides[session_id].session_id is not None: - return self.new_session(self.registered_ides[session_id], session_id=session_id) + return await self.new_session(self.registered_ides[session_id], session_id=session_id) raise KeyError("Session ID not recognized", session_id) return self.sessions[session_id] - def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session: + async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session: full_state = None if session_id is not None and os.path.exists(getSessionFilePath(session_id)): with open(getSessionFilePath(session_id), "r") as f: full_state = FullState(**json.load(f)) - autopilot = DemoAutopilot( + autopilot = await DemoAutopilot.create( policy=DemoPolicy(), ide=ide, full_state=full_state) session_id = session_id or str(uuid4()) ide.session_id = session_id diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 14e9b854..7f23e333 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -181,7 +181,7 @@ function StepContainer(props: StepContainerProps) { } className="overflow-hidden cursor-pointer" onClick={(e) => { - if (e.metaKey) { + if (isMetaEquivalentKeyPressed(e)) { props.onToggleAll(); } else { props.onToggle(); diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index ea5727f0..c724697d 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -81,7 +81,11 @@ const TextDialog = (props: { rows={10} ref={textAreaRef} onKeyDown={(e) => { - if (e.key === "Enter" && e.metaKey && textAreaRef.current) { + if ( + e.key === "Enter" && + isMetaEquivalentKeyPressed(e) && + textAreaRef.current + ) { props.onEnter(textAreaRef.current.value); setText(""); } else if (e.key === "Escape") { diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 57cebac3..cb0404ab 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -137,12 +137,12 @@ function GUI(props: GUIProps) { useEffect(() => { const listener = (e: any) => { // Cmd + i to toggle fast model - if (e.key === "i" && e.metaKey && e.shiftKey) { + if (e.key === "i" && isMetaEquivalentKeyPressed(e) && e.shiftKey) { setUsingFastModel((prev) => !prev); // Cmd + backspace to stop currently running step } else if ( e.key === "Backspace" && - e.metaKey && + isMetaEquivalentKeyPressed(e) && typeof history?.current_index !== "undefined" && history.timeline[history.current_index]?.active ) { @@ -220,7 +220,7 @@ function GUI(props: GUIProps) { if (mainTextInputRef.current) { let input = (mainTextInputRef.current as any).inputValue; // cmd+enter to /edit - if (event?.metaKey) { + if (isMetaEquivalentKeyPressed(event)) { input = `/edit ${input}`; } (mainTextInputRef.current as any).setInputValue(""); diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts new file mode 100644 index 00000000..ad711321 --- /dev/null +++ b/extension/react-app/src/util/index.ts @@ -0,0 +1,30 @@ +type Platform = "mac" | "linux" | "windows" | "unknown"; + +function getPlatform(): Platform { + const platform = window.navigator.platform.toUpperCase(); + if (platform.indexOf("MAC") >= 0) { + return "mac"; + } else if (platform.indexOf("LINUX") >= 0) { + return "linux"; + } else if (platform.indexOf("WIN") >= 0) { + return "windows"; + } else { + return "unknown"; + } +} + +function isMetaEquivalentKeyPressed(event: { + metaKey: boolean; + ctrlKey: boolean; +}): boolean { + const platform = getPlatform(); + switch (platform) { + case "mac": + return event.metaKey; + case "linux": + case "windows": + return event.ctrlKey; + default: + return event.metaKey; + } +} -- cgit v1.2.3-70-g09d2 From 45ee33f7fd84c0bc49d35d9d1a7a3a8e9f6addd7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 15 Jul 2023 15:06:32 -0700 Subject: use correct label for meta key --- extension/react-app/src/components/ComboBox.tsx | 3 ++- .../react-app/src/components/StepContainer.tsx | 7 +++++- extension/react-app/src/util/index.ts | 17 +++++++++++-- extension/src/diffs.ts | 3 ++- extension/src/lang-server/codeLens.ts | 7 +++--- extension/src/util/util.ts | 29 ++++++++++++++++++++++ 6 files changed, 58 insertions(+), 8 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 754c9445..f11e07af 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -13,6 +13,7 @@ import HeaderButtonWithText from "./HeaderButtonWithText"; import { DocumentPlus } from "@styled-icons/heroicons-outline"; import { HighlightedRangeContext } from "../../../schema/FullState"; import { postVscMessage } from "../vscode"; +import { getMetaKeyLabel } from "../util"; // #region styled components const mainInputFontSize = 13; @@ -286,7 +287,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { )} - { - setHoveringContextDropdown(true); - }} - onMouseLeave={() => { - setHoveringContextDropdown(false); - }} - hidden={true || (!hoveringContextDropdown && !hoveringButton)} - > - {highlightedCodeSections.map((section, idx) => ( - <> -

{section.display_name}

- - {section.range.contents} - - - ))} -
); }); -- cgit v1.2.3-70-g09d2 From 88c1f16c597e0a55271e622a5283562ccb7a80a1 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 17 Jul 2023 00:00:31 -0700 Subject: only show delete when only one range selected --- extension/react-app/src/components/ComboBox.tsx | 2 ++ extension/react-app/src/components/PillButton.tsx | 34 +++++++++++++---------- 2 files changed, 22 insertions(+), 14 deletions(-) (limited to 'extension/react-app') diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 773fcf72..dbebd534 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -218,6 +218,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { ? "Editing such a large range may be slow" : undefined } + onlyShowDelete={highlightedCodeSections.length <= 1} editing={section.editing} pinned={section.pinned} index={idx} @@ -334,6 +335,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { })} showAbove={showAbove()} ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0} + hidden={!downshiftProps.isOpen || items.length === 0} > {downshiftProps.isOpen && items.map((item, index) => ( diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx index d9d779d1..5a16516e 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -27,7 +27,6 @@ const GridDiv = styled.div` height: 100%; display: grid; grid-gap: 0; - grid-template-columns: 1fr 1fr; align-items: center; border-radius: ${defaultBorderRadius}; @@ -69,6 +68,7 @@ interface PillButtonProps { editing: boolean; pinned: boolean; warning?: string; + onlyShowDelete?: boolean; } const PillButton = (props: PillButtonProps) => { @@ -105,19 +105,25 @@ const PillButton = (props: PillButtonProps) => { }} > {isHovered && ( - - { - client?.setEditingAtIndices([props.index]); - }} - > - - + + {props.onlyShowDelete || ( + { + client?.setEditingAtIndices([props.index]); + }} + > + + + )} {/* Date: Mon, 17 Jul 2023 11:36:21 -0500 Subject: align on `code section` --- continuedev/src/continuedev/core/policy.py | 2 +- extension/react-app/src/components/ComboBox.tsx | 6 +++--- extension/react-app/src/components/PillButton.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'extension/react-app') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index bc897357..d007c92b 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -58,7 +58,7 @@ class DemoPolicy(Policy): if history.get_current() is None: return ( MessageStep(name="Welcome to Continue", message=dedent("""\ - - Highlight code and ask a question or give instructions + - Highlight code section and ask a question or give instructions - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue - Use `/help` to ask questions about how to use Continue""")) >> WelcomeStep() >> diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index dbebd534..8136399a 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -245,11 +245,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { props.onToggleAddContext(); }} > - Highlight to Add Context + Highlight code section ) : ( { props.onToggleAddContext(); }} @@ -261,7 +261,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {