Merge pull request #113 from voideditor/monaco-editor

Misc Improvements
This commit is contained in:
Andrew Pareles 2024-10-21 14:45:30 -07:00 committed by GitHub
commit 6c0f17d8f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 378 additions and 236 deletions

View file

@ -7,12 +7,10 @@
"": { "": {
"name": "void", "name": "void",
"version": "0.0.1", "version": "0.0.1",
"dependencies": {
"@anthropic-ai/sdk": "^0.27.1",
"openai": "^4.57.0"
},
"devDependencies": { "devDependencies": {
"@anthropic-ai/sdk": "^0.29.2",
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",
"@monaco-editor/react": "^4.6.0",
"@types/diff": "^5.2.2", "@types/diff": "^5.2.2",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
@ -36,6 +34,7 @@
"globals": "^15.9.0", "globals": "^15.9.0",
"marked": "^14.1.0", "marked": "^14.1.0",
"ollama": "^0.5.9", "ollama": "^0.5.9",
"openai": "^4.68.1",
"postcss": "^8.4.41", "postcss": "^8.4.41",
"posthog-js": "^1.174.0", "posthog-js": "^1.174.0",
"react": "^18.3.1", "react": "^18.3.1",
@ -66,9 +65,10 @@
} }
}, },
"node_modules/@anthropic-ai/sdk": { "node_modules/@anthropic-ai/sdk": {
"version": "0.27.3", "version": "0.29.2",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.29.2.tgz",
"integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", "integrity": "sha512-5dwiOPO/AZvhY4bJIG9vjFKU9Kza3hA6VEsbIQg6L9vny2RQIpCFhV50nB9IrG2edZaHZb4HuQ9Wmsn5zgWyZg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
@ -84,6 +84,7 @@
"version": "18.19.50", "version": "18.19.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
"integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -93,6 +94,7 @@
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@ -556,6 +558,34 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@monaco-editor/loader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz",
"integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==",
"dev": true,
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
},
"peerDependencies": {
"monaco-editor": ">= 0.21.0 < 1"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz",
"integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.4.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -728,6 +758,7 @@
"version": "22.6.1", "version": "22.6.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz",
"integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==", "integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
@ -737,6 +768,7 @@
"version": "2.6.11", "version": "2.6.11",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
@ -1068,6 +1100,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"event-target-shim": "^5.0.0" "event-target-shim": "^5.0.0"
@ -1116,6 +1149,7 @@
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"humanize-ms": "^1.2.1" "humanize-ms": "^1.2.1"
@ -1353,6 +1387,7 @@
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
@ -1920,6 +1955,7 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
@ -2163,6 +2199,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
@ -2821,6 +2858,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -3088,6 +3126,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
@ -3102,6 +3141,7 @@
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/format": { "node_modules/format": {
@ -3117,6 +3157,7 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"node-domexception": "1.0.0", "node-domexception": "1.0.0",
@ -3614,6 +3655,7 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.0.0" "ms": "^2.0.0"
@ -5292,6 +5334,7 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@ -5301,6 +5344,7 @@
"version": "2.1.35", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
@ -5532,10 +5576,19 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.52.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz",
"integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mz": { "node_modules/mz": {
@ -5580,6 +5633,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5599,6 +5653,7 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
@ -5793,9 +5848,10 @@
} }
}, },
"node_modules/openai": { "node_modules/openai": {
"version": "4.63.0", "version": "4.68.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.63.0.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-4.68.1.tgz",
"integrity": "sha512-Y9V4KODbmrOpqiOmCDVnPfMxMqKLOx8Hwcdn/r8mePq4yv7FSXGnxCs8/jZKO7zCB/IVPWihpJXwJNAIOEiZ2g==", "integrity": "sha512-C9XmYRHgra1U1G4GGFNqRHQEjxhoOWbQYR85IibfJ0jpHUhOm4/lARiKaC/h3zThvikwH9Dx/XOKWPNVygIS3g==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
@ -5822,6 +5878,7 @@
"version": "18.19.50", "version": "18.19.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
"integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -5831,6 +5888,7 @@
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/optionator": { "node_modules/optionator": {
@ -7254,6 +7312,13 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"dev": true,
"license": "MIT"
},
"node_modules/stdin-discarder": { "node_modules/stdin-discarder": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
@ -7741,6 +7806,7 @@
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/trim-lines": { "node_modules/trim-lines": {
@ -7946,6 +8012,7 @@
"version": "6.19.8", "version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unified": { "node_modules/unified": {
@ -8152,6 +8219,7 @@
"version": "4.0.0-beta.3", "version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
@ -8168,6 +8236,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true,
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/whatwg-fetch": { "node_modules/whatwg-fetch": {
@ -8181,6 +8250,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tr46": "~0.0.3", "tr46": "~0.0.3",

View file

@ -24,7 +24,7 @@
}, },
{ {
"command": "void.ctrl+k", "command": "void.ctrl+k",
"title": "Show Selection Lens" "title": "Make Inline Edit"
}, },
{ {
"command": "void.acceptDiff", "command": "void.acceptDiff",
@ -101,14 +101,16 @@
} }
}, },
"scripts": { "scripts": {
"vscode:prepublish": "npm run compile", "vscode:prepublish": "echo \"running prepublish\"",
"watch": "tsc -watch -p ./", "watch": "tsc -watch -p ./",
"build": "rimraf dist && node build-tsx.js && node build-css.js", "build": "rimraf dist && node build-tsx.js && node build-css.js",
"pretest": "tsc -p ./ && eslint src --ext ts", "pretest": "tsc -p ./ && eslint src --ext ts",
"test": "vscode-test" "test": "vscode-test"
}, },
"devDependencies": { "devDependencies": {
"@anthropic-ai/sdk": "^0.29.2",
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",
"@monaco-editor/react": "^4.6.0",
"@types/diff": "^5.2.2", "@types/diff": "^5.2.2",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
@ -132,6 +134,7 @@
"globals": "^15.9.0", "globals": "^15.9.0",
"marked": "^14.1.0", "marked": "^14.1.0",
"ollama": "^0.5.9", "ollama": "^0.5.9",
"openai": "^4.68.1",
"postcss": "^8.4.41", "postcss": "^8.4.41",
"posthog-js": "^1.174.0", "posthog-js": "^1.174.0",
"react": "^18.3.1", "react": "^18.3.1",
@ -143,9 +146,5 @@
"typescript": "5.5.4", "typescript": "5.5.4",
"typescript-eslint": "^8.3.0", "typescript-eslint": "^8.3.0",
"uuid": "^10.0.0" "uuid": "^10.0.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.27.1",
"openai": "^4.57.0"
} }
} }

View file

@ -2,7 +2,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
function getNonce() { function generateNonce() {
let text = ""; let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) { for (let i = 0; i < 32; i++) {
@ -39,7 +39,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/index.js')); const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/index.js'));
const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/styles.css')); const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/styles.css'));
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri)); const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri));
const nonce = getNonce(); const nonce = generateNonce();
const webviewHTML = `<!DOCTYPE html> const webviewHTML = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -53,6 +53,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<div id="ctrlkroot"></div>
<script nonce="${nonce}" src="${scriptUri}"></script> <script nonce="${nonce}" src="${scriptUri}"></script>
</body> </body>
</html>`; </html>`;

View file

@ -41,6 +41,7 @@ type Diff = {
// editor -> sidebar // editor -> sidebar
type MessageToSidebar = ( type MessageToSidebar = (
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor | { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
| { type: 'ctrl+k', selection: CodeSelection }
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] } | { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
| { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig } | { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig }
| { type: 'allThreads', threads: ChatThreads } | { type: 'allThreads', threads: ChatThreads }
@ -65,7 +66,8 @@ type MessageFromSidebar = (
type ChatThreads = { type ChatThreads = {
[id: string]: { [id: string]: {
id: string; // store the id here too id: string; // store the id here too
createdAt: string; createdAt: string; // ISO string
lastModified: string; // ISO string
messages: ChatMessage[]; messages: ChatMessage[];
} }
} }

View file

@ -9,37 +9,60 @@ const readFileContentOfUri = async (uri: vscode.Uri) => {
.replace(/\r\n/g, '\n') // replace windows \r\n with \n .replace(/\r\n/g, '\n') // replace windows \r\n with \n
} }
const roundRangeToLines = (selection: vscode.Selection) => {
return new vscode.Range(selection.start.line, 0, selection.end.line, Number.MAX_SAFE_INTEGER)
}
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
// 1. Mount the chat sidebar // 1. Mount the chat sidebar
const webviewProvider = new SidebarWebviewProvider(context); const sidebarWebviewProvider = new SidebarWebviewProvider(context);
context.subscriptions.push( context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } }) vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, sidebarWebviewProvider, { webviewOptions: { retainContextWhenHidden: true } })
); );
// 2. Activate the sidebar on ctrl+l // 2. ctrl+l
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('void.ctrl+l', () => { vscode.commands.registerCommand('void.ctrl+l', () => {
const editor = vscode.window.activeTextEditor const editor = vscode.window.activeTextEditor
if (!editor) if (!editor) return
return
// show the sidebar // show the sidebar
vscode.commands.executeCommand('workbench.view.extension.voidViewContainer'); vscode.commands.executeCommand('workbench.view.extension.voidViewContainer');
// vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar // vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar
// get the text the user is selecting
const selectionStr = editor.document.getText(editor.selection);
// get the range of the selection // get the range of the selection
const selectionRange = editor.selection; const selectionRange = roundRangeToLines(editor.selection);
// get the text the user is selecting
const selectionStr = editor.document.getText(selectionRange);
// get the file the user is in // get the file the user is in
const filePath = editor.document.uri; const filePath = editor.document.uri;
// send message to the webview (Sidebar.tsx) // send message to the webview (Sidebar.tsx)
webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar)); sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
})
);
// 2.5: ctrl+k
context.subscriptions.push(
vscode.commands.registerCommand('void.ctrl+k', () => {
console.log('CTRLK PRESSED')
const editor = vscode.window.activeTextEditor
if (!editor) return
// get the range of the selection
const selectionRange = roundRangeToLines(editor.selection);
// get the text the user is selecting
const selectionStr = editor.document.getText(selectionRange);
// get the file the user is in
const filePath = editor.document.uri;
// send message to the webview (Sidebar.tsx)
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+k', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
}) })
); );
@ -56,7 +79,7 @@ export function activate(context: vscode.ExtensionContext) {
})); }));
// 5. Receive messages from sidebar // 5. Receive messages from sidebar
webviewProvider.webview.then( sidebarWebviewProvider.webview.then(
webview => { webview => {
// top navigation bar commands // top navigation bar commands
@ -83,7 +106,8 @@ export function activate(context: vscode.ExtensionContext) {
// send contents to webview // send contents to webview
webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar) webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar)
} else if (m.type === 'applyChanges') { }
else if (m.type === 'applyChanges') {
const editor = vscode.window.activeTextEditor const editor = vscode.window.activeTextEditor
if (!editor) { if (!editor) {

View file

@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { useOnVSCodeMessage } from './getVscodeApi';
export const CtrlK = () => {
const [x, sx] = useState('abc')
useOnVSCodeMessage('ctrl+k', () => {
console.log('Ctrl+K pressed')
sx('Pressed ctrl+k')
})
return <>
<div>
{x}
</div>
</>
};

View file

@ -5,58 +5,45 @@ import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMess
import { SidebarThreadSelector } from "./SidebarThreadSelector"; import { SidebarThreadSelector } from "./SidebarThreadSelector";
import { SidebarChat } from "./SidebarChat"; import { SidebarChat } from "./SidebarChat";
import { SidebarSettings } from './SidebarSettings'; import { SidebarSettings } from './SidebarSettings';
import { identifyUser, useMetrics } from "./metrics/posthog"; import { identifyUser } from "./metrics/posthog";
const Sidebar = () => { const Sidebar = () => {
useMetrics() const chatInputRef = useRef<HTMLTextAreaElement | null>(null)
// when we get the deviceid, identify the user
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getDeviceId' });
awaitVSCodeResponse('deviceId').then((m => {
identifyUser(m.deviceId)
}))
}, [])
const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat') const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat')
// if they pressed the + to add a new chat // if they pressed the + to add a new chat
useOnVSCodeMessage('startNewThread', (m) => { setTab('chat') }) useOnVSCodeMessage('startNewThread', (m) => {
setTab('chat');
chatInputRef.current?.focus();
})
// ctrl+l should switch back to chat // ctrl+l should switch back to chat
useOnVSCodeMessage('ctrl+l', (m) => { setTab('chat') }) useOnVSCodeMessage('ctrl+l', (m) => {
setTab('chat');
chatInputRef.current?.focus();
})
// if they toggled thread selector // if they toggled thread selector
useOnVSCodeMessage('toggleThreadSelector', (m) => { useOnVSCodeMessage('toggleThreadSelector', (m) => {
if (tab === 'threadSelector') if (tab === 'threadSelector') {
setTab('chat') setTab('chat')
else chatInputRef.current?.blur();
} else
setTab('threadSelector') setTab('threadSelector')
}) })
// if they toggled settings // if they toggled settings
useOnVSCodeMessage('toggleSettings', (m) => { useOnVSCodeMessage('toggleSettings', (m) => {
if (tab === 'settings') if (tab === 'settings') {
setTab('chat') setTab('chat')
else chatInputRef.current?.blur();
} else
setTab('settings') setTab('settings')
}) })
// Receive messages from the VSCode extension
useEffect(() => {
const listener = (event: MessageEvent) => {
const m = event.data as MessageToSidebar;
onMessageFromVSCode(m)
}
window.addEventListener('message', listener);
return () => { window.removeEventListener('message', listener) }
}, [])
return <> return <>
<div className={`flex flex-col h-screen w-full`}> <div className={`flex flex-col h-screen w-full`}>
@ -65,7 +52,7 @@ const Sidebar = () => {
</div> </div>
<div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}> <div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarChat /> <SidebarChat chatInputRef={chatInputRef} />
</div> </div>
<div className={`${tab !== 'settings' ? 'hidden' : ''}`}> <div className={`${tab !== 'settings' ? 'hidden' : ''}`}>

View file

@ -4,7 +4,6 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from "reac
import { marked } from 'marked'; import { marked } from 'marked';
import MarkdownRender from "./markdown/MarkdownRender"; import MarkdownRender from "./markdown/MarkdownRender";
import BlockCode from "./markdown/BlockCode"; import BlockCode from "./markdown/BlockCode";
import { SelectedFiles } from "./components/SelectedFiles";
import { File, ChatMessage, CodeSelection } from "../common/shared_types"; import { File, ChatMessage, CodeSelection } from "../common/shared_types";
import * as vscode from 'vscode' import * as vscode from 'vscode'
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi"; import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi";
@ -63,6 +62,55 @@ Please edit the selected code following these instructions:
return str; return str;
}; };
const getBasename = (pathStr: string) => {
// "unixify" path
pathStr = pathStr.replace(/[/\\]+/g, "/") // replace any / or \ or \\ with /
const parts = pathStr.split("/") // split on /
return parts[parts.length - 1]
}
export const SelectedFiles = ({ files, setFiles, }: { files: vscode.Uri[], setFiles: null | ((files: vscode.Uri[]) => void) }) => {
return (
files.length !== 0 && (
<div className="flex flex-wrap -mx-1 -mb-1">
{files.map((filename, i) => (
<button
key={filename.path}
disabled={!setFiles}
className={`btn btn-secondary btn-sm border border-vscode-input-border rounded flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default`}
type="button"
onClick={() => setFiles?.([...files.slice(0, i), ...files.slice(i + 1, Infinity)])}
>
<span>{getBasename(filename.fsPath)}</span>
{/* X button */}
{!!setFiles && <span className="">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="size-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</span>}
</button>
))}
</div>
)
)
}
const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
const role = chatMessage.role const role = chatMessage.role
@ -76,16 +124,17 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
if (role === 'user') { if (role === 'user') {
chatbubbleContents = <> chatbubbleContents = <>
<SelectedFiles files={chatMessage.files} setFiles={null} /> <SelectedFiles files={chatMessage.files} setFiles={null} />
{chatMessage.selection?.selectionStr && <BlockCode text={chatMessage.selection.selectionStr} hideToolbar />} {chatMessage.selection?.selectionStr && <BlockCode
text={chatMessage.selection.selectionStr}
buttonsOnHover={null}
/>}
{children} {children}
</> </>
} }
else if (role === 'assistant') { else if (role === 'assistant') {
chatbubbleContents = <MarkdownRender string={children} /> // sectionsHTML chatbubbleContents = <MarkdownRender string={children} /> // sectionsHTML
} }
return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}> return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}>
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full`}> <div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full`}>
{chatbubbleContents} {chatbubbleContents}
@ -95,7 +144,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
export const SidebarChat = () => { export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HTMLTextAreaElement> }) => {
// state of current message // state of current message
@ -224,8 +273,8 @@ export const SidebarChat = () => {
abortFnRef.current?.() abortFnRef.current?.()
// if messageStream was not empty, add it to the history // if messageStream was not empty, add it to the history
const llmContent = messageStream || '(canceled)' const llmContent = messageStream || '(null)'
const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent } const newHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream, }
addMessageToHistory(newHistoryElt) addMessageToHistory(newHistoryElt)
setMessageStream('') setMessageStream('')
@ -233,14 +282,9 @@ export const SidebarChat = () => {
}, [captureChatEvent, messageStream, addMessageToHistory]) }, [captureChatEvent, messageStream, addMessageToHistory])
//Clear code selection
const clearSelection = () => {
setSelection(null);
};
return <> return <>
<div className="overflow-y-auto overflow-x-hidden space-y-4"> <div className="overflow-x-hidden space-y-4">
{/* previous messages */} {/* previous messages */}
{currentThread !== null && currentThread.messages.map((message, i) => {currentThread !== null && currentThread.messages.map((message, i) =>
<ChatBubble key={i} chatMessage={message} /> <ChatBubble key={i} chatMessage={message} />
@ -261,14 +305,15 @@ export const SidebarChat = () => {
<SelectedFiles files={files} setFiles={setFiles} /> <SelectedFiles files={files} setFiles={setFiles} />
{/* selected code */} {/* selected code */}
{!!selection?.selectionStr && ( {!!selection?.selectionStr && (
<BlockCode className="rounded bg-vscode-sidebar-bg" text={selection.selectionStr} toolbar={( <BlockCode text={selection.selectionStr}
<button buttonsOnHover={(
onClick={clearSelection} <button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded" onClick={() => setSelection(null)}
> className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
Remove >
</button> Remove
)} /> </button>
)} />
)} )}
</div>} </div>}
@ -284,6 +329,7 @@ export const SidebarChat = () => {
{/* input */} {/* input */}
<textarea <textarea
ref={chatInputRef}
onChange={(e) => { setInstructions(e.target.value) }} onChange={(e) => { setInstructions(e.target.value) }}
className="w-full p-2 leading-tight resize-none max-h-[50vh] overflow-hidden bg-transparent border-none !outline-none" className="w-full p-2 leading-tight resize-none max-h-[50vh] overflow-hidden bg-transparent border-none !outline-none"
placeholder="Ctrl+L to select" placeholder="Ctrl+L to select"
@ -321,11 +367,10 @@ export const SidebarChat = () => {
</div> </div>
</div> </div>
<div> {/* error message */}
{!latestError ? null : <div>
{latestError} {latestError}
</div>}
{}
</div>
</div> </div>
</> </>
} }

View file

@ -11,7 +11,7 @@ const SettingOfFieldAndParam = ({ field, param }: { field: VoidConfigField, para
const resetButton = <button const resetButton = <button
disabled={val === defaultVal} disabled={val === defaultVal}
title={val === defaultVal ? 'This is already the default value.' : `Revert value to '${defaultVal}'?`} title={val === defaultVal ? 'This is the default value.' : `Revert value to '${defaultVal}'?`}
className='group btn btn-sm disabled:opacity-75 disabled:cursor-default' className='group btn btn-sm disabled:opacity-75 disabled:cursor-default'
onClick={() => updateState(defaultVal)} onClick={() => updateState(defaultVal)}
> >

View file

@ -15,7 +15,7 @@ export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
const { allThreads, currentThread, switchToThread } = useThreads() const { allThreads, currentThread, switchToThread } = useThreads()
// sorted by most recent to least recent // sorted by most recent to least recent
const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].createdAt > allThreads![threadId2].createdAt ? -1 : 1) const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? 1 : -1)
return ( return (
<div className="flex flex-col gap-y-1"> <div className="flex flex-col gap-y-1">

View file

@ -1,48 +0,0 @@
import React from "react"
import * as vscode from "vscode"
const getBasename = (pathStr: string) => {
// "unixify" path
pathStr = pathStr.replace(/[/\\]+/g, "/") // replace any / or \ or \\ with /
const parts = pathStr.split("/") // split on /
return parts[parts.length - 1]
}
export const SelectedFiles = ({ files, setFiles, }: { files: vscode.Uri[], setFiles: null | ((files: vscode.Uri[]) => void) }) => {
return (
files.length !== 0 && (
<div className="flex flex-wrap -mx-1 -mb-1">
{files.map((filename, i) => (
<button
key={filename.path}
disabled={!setFiles}
className={`btn btn-secondary btn-sm border border-vscode-input-border rounded flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default`}
type="button"
onClick={() => setFiles?.([...files.slice(0, i), ...files.slice(i + 1, Infinity)])}
>
<span>{getBasename(filename.fsPath)}</span>
{/* X button */}
{!!setFiles && <span className="">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="size-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</span>}
</button>
))}
</div>
)
)
}

View file

@ -216,7 +216,7 @@ const voidConfigInfo: Record<
apikey: configString('OpenRouter API key.', ''), apikey: configString('OpenRouter API key.', ''),
}, },
openAICompatible: { openAICompatible: {
endpoint: configString('The endpoint.', 'http://127.0.0.1:11434/v1'), endpoint: configString('The baseUrl (exluding /chat/completions).', 'http://127.0.0.1:11434/v1'),
model: configString('The name of the model to use.', 'gpt-4o'), model: configString('The name of the model to use.', 'gpt-4o'),
apikey: configString('Your API key.', ''), apikey: configString('Your API key.', ''),
}, },

View file

@ -14,11 +14,15 @@ type ConfigForThreadsValueType = {
const ThreadsContext = createContext<ConfigForThreadsValueType>(undefined as unknown as ConfigForThreadsValueType) const ThreadsContext = createContext<ConfigForThreadsValueType>(undefined as unknown as ConfigForThreadsValueType)
const createNewThread = () => ({ const createNewThread = () => {
id: new Date().getTime().toString(), const now = new Date().toISOString()
createdAt: new Date().toISOString(), return {
messages: [], id: new Date().getTime().toString(),
}) createdAt: now,
lastModified: now,
messages: [],
}
}
// const [stateRef, setState] = useInstantState(initVal) // const [stateRef, setState] = useInstantState(initVal)
@ -67,6 +71,7 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
...allThreads.current, ...allThreads.current,
[currentThread.id]: { [currentThread.id]: {
...currentThread, ...currentThread,
lastModified: new Date().toISOString(),
messages: [...currentThread.messages, message], messages: [...currentThread.messages, message],
} }
}) })

View file

@ -8,6 +8,7 @@ type Command = MessageToSidebar['type']
// messageType -> res[] // messageType -> res[]
const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = { const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
"ctrl+l": [], "ctrl+l": [],
"ctrl+k": [],
"files": [], "files": [],
"partialVoidConfig": [], "partialVoidConfig": [],
"startNewThread": [], "startNewThread": [],
@ -20,6 +21,7 @@ const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
// messageType -> id -> res // messageType -> id -> res
const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = { const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
"ctrl+l": {}, "ctrl+l": {},
"ctrl+k": {},
"files": {}, "files": {},
"partialVoidConfig": {}, "partialVoidConfig": {},
"startNewThread": {}, "startNewThread": {},

View file

@ -1,23 +1,66 @@
import * as React from "react" import * as React from "react"
import { useEffect } from "react"
import * as ReactDOM from "react-dom/client" import * as ReactDOM from "react-dom/client"
import Sidebar from "./Sidebar" import Sidebar from "./Sidebar"
import { CtrlK } from "./CtrlK"
import { ThreadsProvider } from "./contextForThreads" import { ThreadsProvider } from "./contextForThreads"
import { ConfigProvider } from "./contextForConfig" import { ConfigProvider } from "./contextForConfig"
import { MessageToSidebar } from "../common/shared_types"
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi"
import { identifyUser, initPosthog } from "./metrics/posthog"
// mount the sidebar on the id="root" element
if (typeof document === "undefined") { if (typeof document === "undefined") {
console.log("index.tsx error: document was undefined") console.log("index.tsx error: document was undefined")
} }
const rootElement = document.getElementById("root")!
console.log("Void root Element:", rootElement)
const extension = ( const CommonEffects = () => {
<ThreadsProvider> // initialize posthog
useEffect(() => {
initPosthog()
}, [])
// when we get the deviceid, identify the user
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getDeviceId' });
awaitVSCodeResponse('deviceId').then((m => {
identifyUser(m.deviceId)
}))
}, [])
// Receive messages from the VSCode extension
useEffect(() => {
const listener = (event: MessageEvent) => {
const m = event.data as MessageToSidebar;
onMessageFromVSCode(m)
}
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener)
}, [])
return null
}
(() => {
// mount the sidebar on the id="root" element
const rootElement = document.getElementById("root")!
console.log("Void root Element:", rootElement)
const sidebar = (<>
<CommonEffects />
<ThreadsProvider>
<ConfigProvider>
<Sidebar />
</ConfigProvider>
</ThreadsProvider>
<ConfigProvider> <ConfigProvider>
<Sidebar /> <CtrlK />
</ConfigProvider> </ConfigProvider>
</ThreadsProvider>
) </>)
const root = ReactDOM.createRoot(rootElement) const root = ReactDOM.createRoot(rootElement)
root.render(extension) root.render(sidebar)
})();

View file

@ -1,32 +1,9 @@
import React, { ReactNode, useCallback, useEffect, useState } from "react" import React, { ReactNode, useCallback, useEffect, useState } from "react"
import { getVSCodeAPI } from "../getVscodeApi"
import SyntaxHighlighter from "react-syntax-highlighter"; import SyntaxHighlighter from "react-syntax-highlighter";
import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs"; import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs";
enum CopyButtonState {
Copy = "Copy",
Copied = "Copied!",
Error = "Could not copy",
}
const COPY_FEEDBACK_TIMEOUT = 1000 const BlockCode = ({ text, buttonsOnHover, language }: { text: string, buttonsOnHover?: ReactNode, language?: string }) => {
// code block with toolbar (Apply, Copy, etc) at top
const BlockCode = ({
text,
language,
toolbar,
hideToolbar = false,
className,
}: {
text: string
language?: string
toolbar?: ReactNode
hideToolbar?: boolean
className?: string
}) => {
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
const customStyle = { const customStyle = {
...atomOneDarkReasonable, ...atomOneDarkReasonable,
@ -36,56 +13,20 @@ const BlockCode = ({
}, },
} }
useEffect(() => { return (<>
if (copyButtonState !== CopyButtonState.Copy) { <div className={`relative group w-full bg-vscode-sidebar-bg overflow-hidden isolate`}>
setTimeout(() => {
setCopyButtonState(CopyButtonState.Copy)
}, COPY_FEEDBACK_TIMEOUT)
}
}, [copyButtonState])
const onCopy = useCallback(() => { {!toolbar ? null : (
navigator.clipboard.writeText(text).then( <div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200">
() => { <div className="flex space-x-2 p-2">{buttonsOnHover === null ? null : buttonsOnHover}</div>
setCopyButtonState(CopyButtonState.Copied)
},
() => {
setCopyButtonState(CopyButtonState.Error)
}
)
}, [text])
const defaultToolbar = (
<>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={onCopy}
>
{copyButtonState}
</button>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
}}
>
Apply
</button>
</>
)
return (
<div className="relative group">
{!hideToolbar && (
<div className="absolute top-0 right-0 invisible group-hover:visible">
<div className="flex space-x-2 p-2">{toolbar || defaultToolbar}</div>
</div> </div>
)} )}
<div <div
className={`overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg ${!hideToolbar ? "rounded-tl-none" : ""} ${className}`} className={`overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg`}
> >
<SyntaxHighlighter <SyntaxHighlighter
language={language} language={language ?? 'plaintext'} // TODO must auto detect language
style={customStyle} style={customStyle}
className={"rounded-sm"} className={"rounded-sm"}
> >
@ -94,6 +35,7 @@ const BlockCode = ({
</div> </div>
</div> </div>
</>
) )
} }

View file

@ -1,6 +1,57 @@
import React, { JSX } from "react" import React, { JSX, useCallback, useEffect, useState } from "react"
import { marked, MarkedToken, Token, TokensList } from "marked" import { marked, MarkedToken, Token, TokensList } from "marked"
import BlockCode from "./BlockCode" import BlockCode from "./BlockCode"
import { getVSCodeAPI } from "../getVscodeApi"
enum CopyButtonState {
Copy = "Copy",
Copied = "Copied!",
Error = "Could not copy",
}
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CodeButtonsOnHover = ({ text }: { text: string }) => {
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
useEffect(() => {
if (copyButtonState !== CopyButtonState.Copy) {
setTimeout(() => {
setCopyButtonState(CopyButtonState.Copy)
}, COPY_FEEDBACK_TIMEOUT)
}
}, [copyButtonState])
const onCopy = useCallback(() => {
navigator.clipboard.writeText(text).then(
() => {
setCopyButtonState(CopyButtonState.Copied)
},
() => {
setCopyButtonState(CopyButtonState.Error)
}
)
}, [text])
return <>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={onCopy}
>
{copyButtonState}
</button>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
}}
>
Apply
</button>
</>
}
const RenderToken = ({ token, nested = false }: { token: Token | string, nested?: boolean }): JSX.Element => { const RenderToken = ({ token, nested = false }: { token: Token | string, nested?: boolean }): JSX.Element => {
@ -12,7 +63,11 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested?
} }
if (t.type === "code") { if (t.type === "code") {
return <BlockCode text={t.text} language={t.lang} /> return <BlockCode
text={t.text}
language={t.lang}
buttonsOnHover={<CodeButtonsOnHover text={t.text} />}
/>
} }
if (t.type === "heading") { if (t.type === "heading") {

View file

@ -1,5 +1,4 @@
import posthog from 'posthog-js' import posthog from 'posthog-js'
import { useEffect } from 'react'
export const identifyUser = (id: string) => { export const identifyUser = (id: string) => {
@ -10,16 +9,12 @@ export const captureEvent = (eventId: string, properties: object) => {
posthog.capture(eventId, properties) posthog.capture(eventId, properties)
} }
export const useMetrics = () => { export const initPosthog = () => {
// We send absolutely no code to the server. We only track usage metrics like button clicks, etc. This might change and we might eventually add an opt-in or opt-out. // We send absolutely no code to the server. We only track usage metrics like button clicks, etc. This might change and we might eventually add an opt-in or opt-out.
useEffect(() => { posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2',
posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', {
{ api_host: 'https://us.i.posthog.com',
api_host: 'https://us.i.posthog.com', person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar
person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar }
} )
)
}, [])
} }