Merge remote-tracking branch 'origin/model-selection' into model-seln-2

This commit is contained in:
mp 2024-12-11 20:04:37 -08:00
commit 901579a445
59 changed files with 1139 additions and 1174 deletions

View file

@ -70,7 +70,7 @@ If you ran `npm run watch`, the build is done when you see something like this:
<!-- 3. Press <kbd>Ctrl+Shift+B</kbd> to start the build process. -->
4. In a new terminal, run `./scripts/code.sh` (Mac/Linux) or `./scripts/code.bat` (Windows). This should open up the built IDE!
4. In a new terminal, run `./scripts/code.sh` (Mac/Linux) or `./scripts/code.bat` (Windows). This should open up the built IDE.
You can always press <kbd>Ctrl+Shift+P</kbd> and run "Reload Window" inside the new window to see changes without re-building.
Now that you're set up, feel free to check out our [Issues](https://github.com/voideditor/void/issues) page.
@ -84,10 +84,20 @@ Now that you're set up, feel free to check out our [Issues](https://github.com/v
## Bundling
To bundle the IDE into an executable, run `npm run gulp vscode-darwin-arm64`.
We don't usually recommend bundling - instead, usually you should just build (above). To bundle Void into an executable app, run one of the following commands. This will create a folder named `VSCode-darwin-arm64` (or similar) in the repo's parent's directory. Be patient - compiling can take ~25 minutes.
Here are the full options: `vscode-{win32-ia32 | win32-x64 | darwin-x64 | darwin-arm64 | linux-ia32 | linux-x64 | linux-arm}(-min)`
### Mac
- `npm run gulp vscode-darwin-arm64` - most common (Apple Silicon)
- `npm run gulp vscode-darwin-x64` (Intel)
### Windows
- `npm run gulp vscode-win32-x64` - most common
- `npm run gulp vscode-win32-ia32`
### Linux
- `npm run gulp vscode-linux-x64` - most common
- `npm run gulp vscode-linux-arm`
- `npm run gulp vscode-linux-ia32`
## Roadmap

View file

@ -273,7 +273,7 @@ function createServerHost(
try {
fs.createDirectory(pathMapper.toResource(path));
} catch (error) {
logger.logNormal('Error fs.createDirectory', { path, error: error + '' });
logger.logNormal('Error fs.createDirectory', { path, error });
}
},
getExecutingFilePath(): string {
@ -323,7 +323,7 @@ function createServerHost(
try {
fs.delete(pathMapper.toResource(path));
} catch (error) {
logger.logNormal('Error fs.deleteFile', { path, error: error + '' });
logger.logNormal('Error fs.deleteFile', { path, error });
}
},
createHash: generateDjb2Hash,

90
package-lock.json generated
View file

@ -10,6 +10,8 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@google/generative-ai": "^0.21.0",
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@parcel/watcher": "2.1.0",
@ -37,6 +39,7 @@
"@xterm/addon-webgl": "^0.19.0-beta.64",
"@xterm/headless": "^5.6.0-beta.64",
"@xterm/xterm": "^5.6.0-beta.64",
"diff": "^7.0.0",
"groq-sdk": "^0.9.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
@ -48,8 +51,13 @@
"native-keymap": "^3.3.5",
"native-watchdog": "^1.4.1",
"node-pty": "1.1.0-beta21",
"ollama": "^0.5.11",
"open": "^8.4.2",
"openai": "^4.76.1",
"posthog-node": "^4.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"tas-client-umd": "0.2.0",
"v8-inspect-profiler": "^0.1.1",
"vscode-oniguruma": "1.7.0",
@ -59,13 +67,12 @@
"yazl": "^2.4.3"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@google/generative-ai": "^0.21.0",
"@playwright/test": "^1.46.1",
"@swc/core": "1.3.62",
"@types/cookie": "^0.3.3",
"@types/debug": "^4.1.5",
"@types/diff": "^6.0.0",
"@types/eslint": "^9.6.1",
"@types/gulp-svgmin": "^1.2.1",
"@types/http-proxy-agent": "^2.0.1",
"@types/kerberos": "^1.1.2",
@ -106,7 +113,6 @@
"cssnano": "^6.0.3",
"debounce": "^1.0.0",
"deemon": "^1.8.0",
"diff": "^7.0.0",
"electron": "30.5.1",
"eslint": "8.36.0",
"eslint-plugin-header": "3.1.1",
@ -150,8 +156,6 @@
"mocha-junit-reporter": "^2.2.1",
"mocha-multi-reporters": "^1.5.1",
"npm-run-all": "^4.1.5",
"ollama": "^0.5.9",
"openai": "^4.71.1",
"opn": "^6.0.0",
"original-fs": "^1.2.0",
"os-browserify": "^0.3.0",
@ -161,11 +165,8 @@
"postcss-nesting": "^12.0.2",
"pump": "^1.0.1",
"rcedit": "^1.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"rimraf": "^2.7.1",
"scope-tailwind": "^1.0.1",
"scope-tailwind": "^1.0.5",
"sinon": "^12.0.1",
"sinon-test": "^3.1.3",
"source-map": "0.6.1",
@ -219,7 +220,6 @@
"version": "0.32.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz",
"integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "^18.11.18",
@ -235,7 +235,6 @@
"version": "18.19.64",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz",
"integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
@ -1003,7 +1002,6 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -1684,7 +1682,6 @@
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz",
"integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.0.0"
@ -3021,6 +3018,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -3060,7 +3068,6 @@
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/unist": "^2"
@ -3247,7 +3254,6 @@
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/vinyl": {
@ -5850,7 +5856,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -5861,7 +5866,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -5872,7 +5876,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -6362,7 +6365,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -7248,7 +7250,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
@ -8497,7 +8498,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"dev": true,
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
@ -9021,7 +9021,6 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"dev": true,
"engines": {
"node": ">=0.4.x"
}
@ -12029,7 +12028,6 @@
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
@ -12040,7 +12038,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/hast": "^2.0.0",
@ -12067,7 +12064,6 @@
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": "*"
@ -12077,7 +12073,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/homedir-polyfill": {
@ -12723,7 +12718,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -12734,7 +12728,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-alphabetical": "^1.0.0",
@ -12946,7 +12939,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -13073,7 +13065,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -13611,8 +13602,7 @@
"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==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@ -14292,7 +14282,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@ -14314,7 +14303,6 @@
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
@ -15984,10 +15972,9 @@
}
},
"node_modules/ollama": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz",
"integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==",
"dev": true,
"version": "0.5.11",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.11.tgz",
"integrity": "sha512-lDAKcpmBU3VAOGF05NcQipHNKTdpKfAHpZ7bjCsElkUkmX7SNZImi6lwIxz/l1zQtLq0S3wuLneRuiXxX2KIew==",
"license": "MIT",
"dependencies": {
"whatwg-fetch": "^3.6.20"
@ -16060,10 +16047,9 @@
}
},
"node_modules/openai": {
"version": "4.71.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.71.1.tgz",
"integrity": "sha512-C6JNMaQ1eijM0lrjiRUL3MgThVP5RdwNAghpbJFdW0t11LzmyqON8Eh8MuUuEZ+CeD6bgYl2Fkn2BoptVxv9Ug==",
"dev": true,
"version": "4.76.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.76.1.tgz",
"integrity": "sha512-ci63/WFEMd6QjjEVeH0pV7hnFS6CCqhgJydSti4Aak/8uo2SpgzKjteUDaY+OkwziVj11mi6j+0mRUIiGKUzWw==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "^18.11.18",
@ -16090,7 +16076,6 @@
"version": "18.19.64",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz",
"integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
@ -16400,7 +16385,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"character-entities": "^1.0.0",
@ -17736,7 +17720,6 @@
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -17770,7 +17753,6 @@
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"dev": true,
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
@ -17961,7 +17943,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@ -17974,7 +17955,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@ -17988,7 +17968,6 @@
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
@ -18206,7 +18185,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hastscript": "^6.0.0",
@ -18222,7 +18200,6 @@
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -18232,7 +18209,6 @@
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true,
"license": "MIT"
},
"node_modules/regex-not": {
@ -18889,7 +18865,6 @@
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@ -18949,9 +18924,9 @@
"dev": true
},
"node_modules/scope-tailwind": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.1.tgz",
"integrity": "sha512-u65hZidTP5LhJL2jufMQwi9ulqdJ6NGx3gYKDwuTYQJz/oaYpOs76cLHCp4ul79KVr7lPWRHTlSqLfs55wrqyA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.5.tgz",
"integrity": "sha512-h7WhPy9LocCjtG0r2ijLJmVa0huJlk8wrlCSEOV08qowsNgFScVMqY8ocIbIfnZvgcOyZlUWtCjO+sf6Q0T9nA==",
"dev": true,
"license": "AGPL-3.0-only",
"dependencies": {
@ -19743,7 +19718,6 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@ -22642,7 +22616,6 @@
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
"dev": true,
"license": "MIT"
},
"node_modules/whatwg-url": {
@ -22876,7 +22849,6 @@
"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"
}

View file

@ -9,7 +9,7 @@
"main": "./out/main",
"private": true,
"scripts": {
"build": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js && cd ../../../../../../../",
"buildreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js && cd ../../../../../../../",
"test": "echo Please run any of the test scripts from the scripts folder.",
"test-browser": "npx playwright install && node test/unit/browser/index.js",
"test-browser-amd": "npx playwright install && node test/unit/browser/index.amd.js",
@ -73,6 +73,8 @@
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@google/generative-ai": "^0.21.0",
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@parcel/watcher": "2.1.0",
@ -100,6 +102,7 @@
"@xterm/addon-webgl": "^0.19.0-beta.64",
"@xterm/headless": "^5.6.0-beta.64",
"@xterm/xterm": "^5.6.0-beta.64",
"diff": "^7.0.0",
"groq-sdk": "^0.9.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
@ -111,8 +114,13 @@
"native-keymap": "^3.3.5",
"native-watchdog": "^1.4.1",
"node-pty": "1.1.0-beta21",
"ollama": "^0.5.11",
"open": "^8.4.2",
"openai": "^4.76.1",
"posthog-node": "^4.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"tas-client-umd": "0.2.0",
"v8-inspect-profiler": "^0.1.1",
"vscode-oniguruma": "1.7.0",
@ -122,13 +130,12 @@
"yazl": "^2.4.3"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@google/generative-ai": "^0.21.0",
"@playwright/test": "^1.46.1",
"@swc/core": "1.3.62",
"@types/cookie": "^0.3.3",
"@types/debug": "^4.1.5",
"@types/diff": "^6.0.0",
"@types/eslint": "^9.6.1",
"@types/gulp-svgmin": "^1.2.1",
"@types/http-proxy-agent": "^2.0.1",
"@types/kerberos": "^1.1.2",
@ -169,7 +176,6 @@
"cssnano": "^6.0.3",
"debounce": "^1.0.0",
"deemon": "^1.8.0",
"diff": "^7.0.0",
"electron": "30.5.1",
"eslint": "8.36.0",
"eslint-plugin-header": "3.1.1",
@ -213,8 +219,6 @@
"mocha-junit-reporter": "^2.2.1",
"mocha-multi-reporters": "^1.5.1",
"npm-run-all": "^4.1.5",
"ollama": "^0.5.9",
"openai": "^4.71.1",
"opn": "^6.0.0",
"original-fs": "^1.2.0",
"os-browserify": "^0.3.0",
@ -224,11 +228,8 @@
"postcss-nesting": "^12.0.2",
"pump": "^1.0.1",
"rcedit": "^1.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"rimraf": "^2.7.1",
"scope-tailwind": "^1.0.1",
"scope-tailwind": "^1.0.5",
"sinon": "^12.0.1",
"sinon-test": "^3.1.3",
"source-map": "0.6.1",

View file

@ -120,6 +120,8 @@ import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/elec
import { normalizeNFC } from '../../base/common/normalization.js';
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js';
import { LLMMessageChannel } from '../../platform/void/electron-main/llmMessageChannel.js';
import { IMetricsService } from '../../platform/void/common/metricsService.js';
import { MetricsMainService } from '../../platform/void/electron-main/metricsMainService.js';
@ -1240,6 +1242,12 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('logger', loggerChannel);
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
// Void
const metricsChannel = ProxyChannel.fromService(accessor.get(IMetricsService), disposables);
mainProcessElectronServer.registerChannel('void-channel-metrics', metricsChannel);
const sendLLMMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
mainProcessElectronServer.registerChannel('void-channel-sendLLMMessage', sendLLMMessageChannel);
// Extension Host Debug Broadcasting
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, LLMMessageServiceParams, ProxyLLMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
@ -10,7 +10,9 @@ import { InstantiationType, registerSingleton } from '../../instantiation/common
import { generateUuid } from '../../../base/common/uuid.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Event } from '../../../base/common/event.js';
import { IDisposable } from '../../../base/common/lifecycle.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { IVoidConfigStateService } from '../common/voidConfigService.js';
import { INotificationService } from '../../notification/common/notification.js';
// BROWSER IMPLEMENTATION OF SENDLLMMESSAGE
@ -19,85 +21,101 @@ export const ISendLLMMessageService = createDecorator<ISendLLMMessageService>('s
// defines an interface that node/ creates and browser/ uses
export interface ISendLLMMessageService {
readonly _serviceBrand: undefined;
sendLLMMessage: (params: LLMMessageServiceParams) => string;
sendLLMMessage: (params: LLMMessageServiceParams) => string | null;
abort: (requestId: string) => void;
}
export class SendLLMMessageService implements ISendLLMMessageService {
export class SendLLMMessageService extends Disposable implements ISendLLMMessageService {
readonly _serviceBrand: undefined;
private readonly channel: IChannel;
private readonly channel: IChannel // LLMMessageChannel
private readonly _disposablesOfRequestId: Record<string, IDisposable[]> = {}
private readonly onTextHooks: { [eventId: string]: ((params: ProxyOnTextPayload) => void) } = {}
private readonly onFinalMessageHooks: { [eventId: string]: ((params: ProxyOnFinalMessagePayload) => void) } = {}
private readonly onErrorHooks: { [eventId: string]: ((params: ProxyOnErrorPayload) => void) } = {}
constructor(
@IMainProcessService mainProcessService: IMainProcessService // used as a renderer (only usable on client side)
@IMainProcessService private readonly mainProcessService: IMainProcessService, // used as a renderer (only usable on client side)
@IVoidConfigStateService private readonly voidConfigStateService: IVoidConfigStateService,
@INotificationService private readonly notificationService: INotificationService,
) {
super()
this.channel = mainProcessService.getChannel('void-channel-sendLLMMessage')
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service, not needed here
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service
this.channel = this.mainProcessService.getChannel('void-channel-sendLLMMessage')
// this sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead
const onTextEvent: Event<ProxyOnTextPayload> = this.channel.listen('onText')
const onFinalMessageEvent: Event<ProxyOnFinalMessagePayload> = this.channel.listen('onFinalMessage')
const onErrorEvent: Event<ProxyOnErrorPayload> = this.channel.listen('onError')
this._register(
onTextEvent(e => {
this.onTextHooks[e.requestId]?.(e)
})
)
this._register(
onFinalMessageEvent(e => {
this.onFinalMessageHooks[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
})
)
this._register(
onErrorEvent(e => {
console.log('Error in SendLLMMessageService:', JSON.stringify(e))
this.onErrorHooks[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
})
)
}
_addDisposable(requestId: string, disposable: IDisposable) {
if (!this._disposablesOfRequestId[requestId]) {
this._disposablesOfRequestId[requestId] = []
}
this._disposablesOfRequestId[requestId].push(disposable)
}
sendLLMMessage(params: LLMMessageServiceParams) {
const requestId_ = generateUuid();
const { onText, onFinalMessage, onError, ...proxyParams } = params;
const { featureName } = proxyParams
// listen for listenerName='onText' | 'onFinalMessage' | 'onError', and call the original function on it
// end early if no provider
const modelSelection = this.voidConfigStateService.state.modelSelectionOfFeature[featureName]
if (modelSelection === null) {
this.notificationService.warn('Please add a Provider in Settings!')
onError({ message: 'Please add a Provider in Settings!', fullError: null })
return null
}
const { providerName, modelName } = modelSelection
const onTextEvent: Event<ProxyOnTextPayload> = this.channel.listen('onText')
this._addDisposable(requestId_,
onTextEvent(e => {
if (requestId_ !== e.requestId) return;
onText(e)
})
)
// add state for request id
const requestId_ = generateUuid();
this.onTextHooks[requestId_] = onText
this.onFinalMessageHooks[requestId_] = onFinalMessage
this.onErrorHooks[requestId_] = onError
const onFinalMessageEvent: Event<ProxyOnFinalMessagePayload> = this.channel.listen('onFinalMessage')
this._addDisposable(requestId_,
onFinalMessageEvent(e => {
if (requestId_ !== e.requestId) return;
onFinalMessage(e)
this._dispose(requestId_)
})
)
const { settingsOfProvider } = this.voidConfigStateService.state
const onErrorEvent: Event<ProxyOnErrorPayload> = this.channel.listen('onError')
this._addDisposable(requestId_,
onErrorEvent(e => {
if (requestId_ !== e.requestId) return;
console.log('event onError', JSON.stringify(e))
onError(e)
this._dispose(requestId_)
})
)
// params will be stripped of all its functions
this.channel.call('sendLLMMessage', { ...proxyParams, requestId: requestId_ } satisfies ProxyLLMMessageParams);
// params will be stripped of all its functions over the IPC channel
this.channel.call('sendLLMMessage', {
...proxyParams,
requestId: requestId_,
providerName,
modelName,
settingsOfProvider,
} satisfies ProxyLLMMessageParams);
return requestId_
}
private _dispose(requestId: string) {
if (!(requestId in this._disposablesOfRequestId)) return
for (const disposable of this._disposablesOfRequestId[requestId]) {
disposable.dispose()
}
delete this._disposablesOfRequestId[requestId]
}
abort(requestId: string) {
this.channel.call('abort', { requestId } satisfies ProxyLLMMessageAbortParams);
this._dispose(requestId)
this._onRequestIdDone(requestId)
}
_onRequestIdDone(requestId: string) {
delete this.onTextHooks[requestId]
delete this.onFinalMessageHooks[requestId]
delete this.onErrorHooks[requestId]
}
}

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';

View file

@ -1,18 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { ProviderName, VoidProviderState } from './configTypes'
import { IRange } from '../../../editor/common/core/range'
import { ProviderName, SettingsOfProvider } from './voidConfigTypes'
// ---------- type definitions ----------
export type OnText = (p: { newText: string, fullText: string }) => void
export type OnFinalMessage = (p: { fullText: string }) => void
export type OnError = (p: { error: Error | string }) => void
export type OnError = (p: { message: string, fullError: Error | null }) => void
export type AbortRef = { current: (() => void) | null }
@ -21,42 +20,67 @@ export type LLMMessage = {
content: string;
}
export type LLMFeatureSelection = {
featureName: 'Ctrl+K',
range: IRange
} | {
featureName: 'Ctrl+L',
} | {
featureName: 'Autocomplete',
range: IRange
}
export type LLMMessageServiceParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidProviderState | null;
logging: {
loggingName: string,
};
providerName: ProviderName;
}
} & LLMFeatureSelection
// params to the true sendLLMMessage function
export type SendLLMMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
abortRef: AbortRef;
messages: LLMMessage[];
voidConfig: VoidProviderState | null;
logging: {
loggingName: string,
};
providerName: ProviderName;
abortRef: AbortRef;
modelName: string;
settingsOfProvider: SettingsOfProvider;
}
// can't send functions across a proxy, use listeners instead
export const listenerNames = ['onText', 'onFinalMessage', 'onError'] as const
export type ProxyLLMMessageParams = Omit<LLMMessageServiceParams, typeof listenerNames[number]> & { requestId: string }
export type BlockedProxyParams = 'onText' | 'onFinalMessage' | 'onError' | 'abortRef'
export type ProxyLLMMessageParams = Omit<SendLLMMMessageParams, BlockedProxyParams> & { requestId: string }
export type ProxyOnTextPayload = Parameters<OnText>[0] & { requestId: string }
export type ProxyOnFinalMessagePayload = Parameters<OnFinalMessage>[0] & { requestId: string }
export type ProxyOnErrorPayload = Parameters<OnError>[0] & { requestId: string }
export type ProxyLLMMessageAbortParams = { requestId: string }
export type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[];
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
settingsOfProvider: SettingsOfProvider;
providerName: ProviderName;
modelName: string;
_setAborter: (aborter: () => void) => void;
}) => void

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export interface IMetricsService {

View file

@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { deepClone } from '../../../base/common/objects.js';
import { IEncryptionService } from '../../encryption/common/encryptionService.js';
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
import { defaultVoidProviderState, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider } from './voidConfigTypes.js';
const STORAGE_KEY = 'void.voidConfigStateII'
type SetSettingOfProviderFn = <K extends ProviderName>(
providerName: K,
option: keyof SettingsOfProvider[K],
newVal: string
) => Promise<void>;
type SetModelSelectionOfFeature = <K extends FeatureName>(
featureName: K,
newVal: ModelSelectionOfFeature[K],
) => Promise<void>;
type VoidConfigState = {
settingsOfProvider: SettingsOfProvider; // optionsOfProvider
modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature
}
export interface IVoidConfigStateService {
readonly _serviceBrand: undefined;
readonly state: VoidConfigState;
onDidChangeState: Event<void>;
onDidGetInitState: Event<void>;
setSettingOfProvider: SetSettingOfProviderFn;
setModelSelectionOfFeature: SetModelSelectionOfFeature;
}
const defaultState = () => {
const d: VoidConfigState = {
settingsOfProvider: deepClone(defaultVoidProviderState),
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null }
}
return d
}
export const IVoidConfigStateService = createDecorator<IVoidConfigStateService>('VoidConfigStateService');
class VoidConfigStateService extends Disposable implements IVoidConfigStateService {
_serviceBrand: undefined;
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
private readonly _onDidGetInitState = new Emitter<void>();
readonly onDidGetInitState: Event<void> = this._onDidGetInitState.event;
state: VoidConfigState;
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IEncryptionService private readonly _encryptionService: IEncryptionService,
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
) {
super()
// at the start, we haven't read the partial config yet, but we need to set state to something
this.state = defaultState()
// read and update the actual state immediately
this._readVoidConfigState().then(voidConfigState => { this._setState(voidConfigState, 'initialState') })
}
private async _readVoidConfigState(): Promise<VoidConfigState> {
const encryptedPartialConfig = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION)
if (!encryptedPartialConfig)
return defaultState()
const voidConfigStateStr = await this._encryptionService.decrypt(encryptedPartialConfig)
return JSON.parse(voidConfigStateStr)
}
private async _storeVoidConfigState(voidConfigState: VoidConfigState) {
const encryptedVoidConfigStr = await this._encryptionService.encrypt(JSON.stringify(voidConfigState))
this._storageService.store(STORAGE_KEY, encryptedVoidConfigStr, StorageScope.APPLICATION, StorageTarget.USER);
}
setSettingOfProvider: SetSettingOfProviderFn = async (providerName, option, newVal) => {
const newState: VoidConfigState = {
...this.state,
settingsOfProvider: {
...this.state.settingsOfProvider,
[providerName]: {
...this.state.settingsOfProvider[providerName],
[option]: newVal,
}
}
}
console.log('NEW STATE I', newState)
await this._storeVoidConfigState(newState)
this._setState(newState)
}
setModelSelectionOfFeature: SetModelSelectionOfFeature = async (featureName, newVal) => {
const newState: VoidConfigState = {
...this.state,
modelSelectionOfFeature: {
...this.state.modelSelectionOfFeature,
[featureName]: newVal
}
}
console.log('NEW STATE II', newState)
await this._storeVoidConfigState(newState)
this._setState(newState)
}
// internal function to update state, should be called every time state changes
private async _setState(voidConfigState: VoidConfigState, type: 'usual' | 'initialState' = 'usual') {
this.state = voidConfigState
if (type === 'usual')
this._onDidChangeState.fire()
else if (type === 'initialState')
this._onDidGetInitState.fire()
}
}
registerSingleton(IVoidConfigStateService, VoidConfigStateService, InstantiationType.Eager);

View file

@ -1,7 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
@ -34,7 +34,7 @@ export const voidProviderDefaults = {
},
openAICompatible: {
apiKey: '',
endpoint: 'http://127.0.0.1:11434/v1',
endpoint: 'https://my-website.com/v1',
},
gemini: {
apiKey: '',
@ -47,7 +47,7 @@ export const voidProviderDefaults = {
export const voidInitModelOptions = {
anthropic: () => ({
model: 'claude-3-5-sonnet-20240620',
// model: 'claude-3-5-sonnet-20240620',
models: [
'claude-3-5-sonnet-20240620',
'claude-3-opus-20240229',
@ -56,7 +56,7 @@ export const voidInitModelOptions = {
],
}),
openAI: () => ({
model: 'gpt-4o',
// model: 'gpt-4o',
models: [
'o1-preview',
'o1-mini',
@ -78,7 +78,7 @@ export const voidInitModelOptions = {
],
}),
ollama: () => ({ // TODO make this do a fetch to get the models
model: 'codestral',
// model: 'codestral',
models: [
'codestral',
'qwen2.5-coder',
@ -177,15 +177,15 @@ export const voidInitModelOptions = {
],
}),
openRouter: () => ({
model: 'openai/gpt-4o',
// model: 'openai/gpt-4o',
models: null, // any
}),
openAICompatible: () => ({
model: 'openai/gpt-4o',
// model: 'openai/gpt-4o',
models: null, // any
}),
gemini: () => ({
model: 'gemini-1.5-flash',
// model: 'gemini-1.5-flash',
models: [
'gemini-1.5-flash',
'gemini-1.5-pro',
@ -194,7 +194,7 @@ export const voidInitModelOptions = {
],
}),
groq: () => ({
model: 'mixtral-8x7b-32768',
// model: 'mixtral-8x7b-32768',
models: [
"mixtral-8x7b-32768",
"llama2-70b-4096",
@ -210,7 +210,8 @@ export const providerNames = Object.keys(voidProviderDefaults) as ProviderName[]
export type VoidProviderState = {
// state
export type SettingsOfProvider = {
[providerName in ProviderName]: (
{
[optionName in keyof typeof voidProviderDefaults[providerName]]: string
@ -221,14 +222,13 @@ export type VoidProviderState = {
maxTokens: string,
models: string[] | null, // if null, user can type in any string as a model
model: string,
})
}
type UnionOfKeys<T> = T extends T ? keyof T : never;
type ProviderSettingName = UnionOfKeys<VoidProviderState[ProviderName]>
export type SettingName = UnionOfKeys<SettingsOfProvider[ProviderName]>
@ -238,23 +238,43 @@ type DisplayInfo = {
placeholder: string,
}
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: ProviderSettingName): DisplayInfo => {
export const titleOfProviderName = (providerName: ProviderName) => {
if (providerName === 'anthropic')
return 'Anthropic'
else if (providerName === 'openAI')
return 'OpenAI'
else if (providerName === 'ollama')
return 'Ollama'
else if (providerName === 'openRouter')
return 'OpenRouter'
else if (providerName === 'openAICompatible')
return 'OpenAI-Compatible'
else if (providerName === 'gemini')
return 'Gemini'
else if (providerName === 'groq')
return 'Groq'
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
}
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
if (settingName === 'apiKey') {
return {
title: 'API Key',
type: 'string',
placeholder: providerName === 'anthropic' ? 'sk-ant-api03-abc123...' :
placeholder: providerName === 'anthropic' ? 'sk-ant-abc123...' : // sk-ant-api03-abc123
providerName === 'openAI' ? 'sk-proj-abc123...' :
providerName === 'openRouter' ? 'sk-or-v1-abc123...' :
providerName === 'openRouter' ? 'sk-or-abc123...' : // sk-or-v1-abc123
providerName === 'gemini' ? 'abc123...' :
providerName === 'groq' ? 'gsk_abc123...' :
'(never)',
providerName === 'openAICompatible' ? 'sk-abc123...' :
'(never)',
}
}
else if (settingName === 'endpoint') {
return {
title: providerName === 'ollama' ? 'The endpoint of your Ollama instance.' :
providerName === 'openAICompatible' ? 'The baseUrl (excluding /chat/completions).'
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
providerName === 'openAICompatible' ? 'Endpoint compatible with OpenAI API' // (do not include /chat/completions)
: '(never)',
type: 'string',
placeholder: providerName === 'ollama' || providerName === 'openAICompatible' ?
@ -269,16 +289,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
placeholder: '1024',
}
}
else if (settingName === 'model') {
return {
title: 'Model',
type: '(never)',
placeholder: '(never)',
}
}
else if (settingName === 'enabled') {
return {
title: 'Enabled',
title: 'Enabled?',
type: 'boolean',
placeholder: '(never)',
}
@ -297,7 +310,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
export const defaultVoidProviderState: VoidProviderState = {
export const defaultVoidProviderState: SettingsOfProvider = {
anthropic: {
...voidProviderDefaults.anthropic,
...voidInitModelOptions.anthropic(),
@ -344,22 +357,21 @@ export const defaultVoidProviderState: VoidProviderState = {
type VoidFeatureState = {
// this is a state
export type ModelSelectionOfFeature = {
'Ctrl+L': {
provider: ProviderName,
model: string,
providerName: ProviderName,
modelName: string,
} | null,
'Ctrl+K': {
provider: ProviderName,
model: string,
providerName: ProviderName,
modelName: string,
} | null,
'Autocomplete': {
provider: ProviderName,
model: string,
providerName: ProviderName,
modelName: string,
} | null,
}
export type FeatureName = keyof VoidFeatureState
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete']
export type FeatureName = keyof ModelSelectionOfFeature
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const

View file

@ -1,14 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import Anthropic from '@anthropic-ai/sdk';
import { parseMaxTokensStr, SendLLMMessageFnTypeInternal } from './util.js';
import { parseMaxTokensStr } from './util.js';
import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
import { displayInfoOfSettingName } from '../../common/voidConfigTypes.js';
// Anthropic
type LLMMessageAnthropic = {
role: 'user' | 'assistant';
content: string;
}
export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
const thisConfig = voidConfig.anthropic
const thisConfig = settingsOfProvider.anthropic
const maxTokens = parseMaxTokensStr(thisConfig.maxTokens)
if (maxTokens === undefined) {
onError({ message: `Please set a value for ${displayInfoOfSettingName('anthropic', 'maxTokens').title}.`, fullError: null })
return
}
const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
@ -21,11 +34,13 @@ export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onTex
// remove system messages for Anthropic
const anthropicMessages = messages.filter(msg => msg.role !== 'system') as LLMMessageAnthropic[]
const stream = anthropic.messages.stream({
system: systemMessage,
messages: anthropicMessages,
model: thisConfig.model,
max_tokens: parseMaxTokensStr(thisConfig.maxTokens)!, // this might be undefined, but it will just throw an error for the user to see
model: modelName,
max_tokens: maxTokens,
});
@ -44,10 +59,10 @@ export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onTex
stream.on('error', (error) => {
// the most common error will be invalid API key (401), so we handle this with a nice message
if (error instanceof Anthropic.APIError && error.status === 401) {
onError({ error: 'Invalid API key.' })
onError({ message: 'Invalid API key.', fullError: error })
}
else {
onError({ error })
onError({ message: error + '', fullError: error }) // anthropic errors can be stringified nicely like this
}
})

View file

@ -1,15 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
import { SendLLMMessageFnTypeInternal } from './util';
import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
// Gemini
export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
let fullText = ''
const thisConfig = voidConfig.gemini
const thisConfig = settingsOfProvider.gemini
const genAI = new GoogleGenerativeAI(thisConfig.apiKey);
const model = genAI.getGenerativeModel({ model: thisConfig.model });
const model = genAI.getGenerativeModel({ model: modelName });
// remove system messages that get sent to Gemini
// str of all system messages
@ -39,10 +44,10 @@ export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, on
})
.catch((error) => {
if (error instanceof GoogleGenerativeAIFetchError && error.status === 400) {
onError({ error: 'Invalid API key.' });
onError({ message: 'Invalid API key.', fullError: null });
}
else {
onError({ error });
onError({ message: error + '', fullError: error });
}
})
}

View file

@ -1,15 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// // Greptile
// // https://docs.greptile.com/api-reference/query
// // https://docs.greptile.com/quickstart#sample-response-streamed
// import { SendLLMMessageFnTypeInternal } from './util';
// import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, _setAborter }) => {
// let fullText = ''
// const thisConfig = voidConfig.greptile
// const thisConfig = settingsOfProvider.greptile
// fetch('https://api.greptile.com/v2/query', {
// method: 'POST',

View file

@ -1,12 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import Groq from 'groq-sdk';
import { SendLLMMessageFnTypeInternal } from './util';
import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
import { parseMaxTokensStr } from './util.js';
// Groq
export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
let fullText = '';
const thisConfig = voidConfig.groq
const thisConfig = settingsOfProvider.groq
const groq = new Groq({
apiKey: thisConfig.apiKey,
@ -16,7 +21,7 @@ export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onTe
await groq.chat.completions
.create({
messages: messages,
model: thisConfig.model,
model: modelName,
stream: true,
temperature: 0.7,
max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
@ -35,7 +40,7 @@ export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onTe
onFinalMessage({ fullText });
})
.catch(error => {
onError({ error });
onError({ message: error + '', fullError: error });
})

View file

@ -1,18 +1,23 @@
import { Ollama, ErrorResponse } from 'ollama';
import { SendLLMMessageFnTypeInternal } from './util';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Ollama } from 'ollama';
import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
import { parseMaxTokensStr } from './util.js';
// Ollama
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
const thisConfig = voidConfig.ollama
const thisConfig = settingsOfProvider.ollama
let fullText = ''
const ollama = new Ollama({ host: thisConfig.endpoint })
ollama.chat({
model: thisConfig.model,
model: modelName,
messages: messages,
stream: true,
options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens
@ -30,15 +35,15 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
})
// when error/fail
.catch((error) => {
if (typeof error === 'object') {
const e = error.error as ErrorResponse['error']
if (e) {
const name = error.name ?? 'Error'
onError({ error: `${name}: ${e}` })
return;
}
}
onError({ error })
// if (typeof error === 'object') {
// const e = error.error as ErrorResponse['error']
// if (e) {
// const name = error.name ?? 'Error'
// onError({ error: `${name}: ${e}` })
// return;
// }
// }
onError({ message: error + '', fullError: error })
})
};

View file

@ -1,10 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import OpenAI from 'openai';
import { SendLLMMessageFnTypeInternal } from './util';
import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
import { parseMaxTokensStr } from './util.js';
// OpenAI, OpenRouter, OpenAICompatible
export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }) => {
export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) => {
let fullText = ''
@ -13,12 +18,12 @@ export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
if (providerName === 'openAI') {
const thisConfig = voidConfig.openAI
const thisConfig = settingsOfProvider.openAI
openai = new OpenAI({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
}
else if (providerName === 'openRouter') {
const thisConfig = voidConfig.openRouter
const thisConfig = settingsOfProvider.openRouter
openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true,
defaultHeaders: {
@ -26,12 +31,12 @@ export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
},
});
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
}
else if (providerName === 'openAICompatible') {
const thisConfig = voidConfig.openAICompatible
const thisConfig = settingsOfProvider.openAICompatible
openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true })
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
}
else {
console.error(`sendOpenAIMsg: invalid providerName: ${providerName}`)
@ -53,10 +58,10 @@ export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
// when error/fail - this catches errors of both .create() and .then(for await)
.catch(error => {
if (error instanceof OpenAI.APIError && error.status === 401) {
onError({ error: 'Invalid API key.' });
onError({ message: 'Invalid API key.', fullError: error });
}
else {
onError({ error });
onError({ message: error, fullError: error });
}
})

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { SendLLMMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes.js';
import { IMetricsService } from '../../common/metricsService.js';
@ -13,14 +18,14 @@ export const sendLLMMessage = ({
onFinalMessage: onFinalMessage_,
onError: onError_,
abortRef: abortRef_,
voidConfig,
logging: { loggingName },
providerName
settingsOfProvider,
providerName,
modelName,
}: SendLLMMMessageParams,
metricsService: IMetricsService
) => {
if (!voidConfig) return;
// trim message content (Anthropic and other providers give an error if there is trailing whitespace)
messages = messages.map(m => ({ ...m, content: m.content.trim() }))
@ -54,11 +59,12 @@ export const sendLLMMessage = ({
onFinalMessage_({ fullText })
}
const onError: OnError = ({ error }) => {
const onError: OnError = ({ message: error, fullError }) => {
if (_didAbort) return
console.log("ERROR!!!!!", error)
console.error('sendLLMMessage onError:', error)
captureChatEvent(`${loggingName} - Error`, { error })
onError_({ error })
onError_({ message: error, fullError })
}
const onAbort = () => {
@ -74,34 +80,31 @@ export const sendLLMMessage = ({
try {
switch (providerName) {
case 'anthropic':
sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
sendAnthropicMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'openAI':
case 'openRouter':
case 'openAICompatible':
sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
sendOpenAIMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'gemini':
sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
sendGeminiMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'ollama':
sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
sendOllamaMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
// case 'greptile':
// sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
// break;
case 'groq':
sendGroqMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName });
sendGroqMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
default:
onError({ error: `Error: whichApi was "${providerName}", which is not recognized!` })
onError({ message: `Error: Void provider was "${providerName}", which is not recognized.`, fullError: null })
break;
}
}
catch (error) {
if (error instanceof Error) { onError({ error }) }
else { onError({ error: `Unexpected Error in sendLLMMessage: ${error}` }); }
if (error instanceof Error) { onError({ message: error + '', fullError: error }) }
else { onError({ message: `Unexpected Error in sendLLMMessage: ${error}`, fullError: error }); }
// ; (_aborter as any)?.()
// _didAbort = true
}

View file

@ -1,5 +1,7 @@
import { ProviderName, VoidProviderState } from '../../common/configTypes'
import { LLMMessage, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes'
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
export const parseMaxTokensStr = (maxTokensStr: string) => {
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
@ -10,13 +12,3 @@ export const parseMaxTokensStr = (maxTokensStr: string) => {
}
export type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[];
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
voidConfig: VoidProviderState;
providerName: ProviderName;
_setAborter: (aborter: () => void) => void;
}) => void

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// registered in app.ts
@ -8,7 +8,7 @@
import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { listenerNames, ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, ProxyLLMMessageParams, AbortRef, SendLLMMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
import { BlockedProxyParams, ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, ProxyLLMMessageParams, AbortRef, SendLLMMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
import { sendLLMMessage } from './llmMessage/sendLLMMessage.js'
import { IMetricsService } from '../common/metricsService.js';
@ -28,12 +28,15 @@ export class LLMMessageChannel implements IServerChannel {
private readonly _abortRefOfRequestId: Record<string, AbortRef> = {}
// stupidly, channels can't take in @IService
constructor(
private readonly metricsService: IMetricsService
) { }
private readonly metricsService: IMetricsService,
) {
}
// browser uses this to listen for changes
listen(_: unknown, event: typeof listenerNames[number]): Event<any> {
listen(_: unknown, event: BlockedProxyParams): Event<any> {
if (event === 'onText') {
return this.onText;
}
@ -68,7 +71,7 @@ export class LLMMessageChannel implements IServerChannel {
}
// the only place sendLLMMessage is actually called
private _callSendLLMMessage(params: ProxyLLMMessageParams) {
private async _callSendLLMMessage(params: ProxyLLMMessageParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId))
@ -78,7 +81,7 @@ export class LLMMessageChannel implements IServerChannel {
...params,
onText: ({ newText, fullText }) => { this._onText.fire({ requestId, newText, fullText }); },
onFinalMessage: ({ fullText }) => { this._onFinalMessage.fire({ requestId, fullText }); },
onError: ({ error }) => { this._onError.fire({ requestId, error }); },
onError: ({ message: error, fullError }) => { console.log('sendLLM: firing err'); this._onError.fire({ requestId, message: error, fullError }); },
abortRef: this._abortRefOfRequestId[requestId],
}
sendLLMMessage(mainThreadParams, this.metricsService);

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
@ -10,18 +10,12 @@ import { IMetricsService } from '../common/metricsService.js';
import { PostHog } from 'posthog-node'
// posthog-js (old):
// posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { api_host: 'https://us.i.posthog.com', })
// const buildEnv = 'development';
// const buildNumber = '1.0.0';
// const isMac = process.platform === 'darwin';
// // TODO use commandKey
// const commandKey = isMac ? '⌘' : 'Ctrl';
// const systemInfo = {
// buildEnv,
// buildNumber,
// isMac,
// }
export class MetricsMainService extends Disposable implements IMetricsService {
_serviceBrand: undefined;
@ -43,9 +37,9 @@ export class MetricsMainService extends Disposable implements IMetricsService {
}
capture: IMetricsService['capture'] = (event, params) => {
console.log('Capturing', { event, params })
console.log('full capture:', { distinctId: this._distinctId, event, properties: params })
this.client.capture({ distinctId: this._distinctId, event, properties: params })
const capture = { distinctId: this._distinctId, event, properties: params } as const
// console.log('full capture:', capture)
this.client.capture(capture)
}
}

View file

@ -742,18 +742,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) });
});
// // Void: send from https://
// this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => {
// // const voidConfig = this.voidConfigStateService.state.voidConfig
// // const whichApi = voidConfig.default['whichApi']
// const endpoint = 'http://127.' //string | undefined = voidConfig[whichApi as VoidConfigField].endpoint
// if (endpoint && details.url.startsWith(endpoint)) {
// details.requestHeaders['Origin'] = 'https://app.voideditor.com'
// }
// cb({ cancel: false, requestHeaders: details.requestHeaders });
// });
}

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { diffLines } from './react/out/util/diffLines.js'

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { OperatingSystem, OS } from '../../../../base/common/platform.js';

View file

@ -1,4 +1,9 @@
.monaco-editor .void-sweepIdxBG {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .void-sweepIdxBG {
background-color: var(--vscode-void-sweepIdxBG);
}

View file

@ -1,3 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { CodeSelection } from '../registerThreads.js';

View file

@ -1,5 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// // used for ctrl+l
// const partialGenerationInstructions = ``
@ -9,6 +11,31 @@
// const fimInstructions = ``
// CTRL+K prompt:
// const promptContent = `Here is the user's original selection:
// \`\`\`
// <MID>${selection}</MID>
// \`\`\`
// The user wants to apply the following instructions to the selection:
// ${instructions}
// Please rewrite the selection following the user's instructions.
// Instructions to follow:
// 1. Follow the user's instructions
// 2. You may ONLY CHANGE the selection, and nothing else in the file
// 3. Make sure all brackets in the new selection are balanced the same was as in the original selection
// 3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
// Complete the following:
// \`\`\`
// <PRE>${prefix}</PRE>
// <SUF>${suffix}</SUF>
// <MID>`;
export const generateDiffInstructions = `
You are a coding assistant. You are given a list of relevant files \`files\`, a selection that the user is making \`selection\`, and instructions to follow \`instructions\`.

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { execSync } from 'child_process';
// clear temp dirs

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { ReactNode } from "react"
import SyntaxHighlighter from "react-syntax-highlighter";
import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs";

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { JSX, useCallback, useEffect, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
@ -44,7 +49,7 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
inlineDiffService.startStreaming({ type: 'ctrl+l', providerName: 'anthropic' }, text)
inlineDiffService.startStreaming({ featureName: 'Ctrl+L' }, text)
}}
>
Apply

View file

@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { ErrorDisplay } from './ErrorDisplay.js';
interface Props {
children: ReactNode;
fallback?: ReactNode;
onDismiss?: () => void;
}
interface State {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error: Error): Partial<State> {
return {
hasError: true,
error
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
this.setState({
error,
errorInfo
});
}
render(): ReactNode {
if (this.state.hasError && this.state.error) {
// If a custom fallback is provided, use it
if (this.props.fallback) {
return this.props.fallback;
}
// Use ErrorDisplay component as the default error UI
return (
<ErrorDisplay
message={this.state.error + ''}
fullError={this.state.error}
onDismiss={this.props.onDismiss || null}
/>
);
}
return this.props.children;
}
}
export default ErrorBoundary;

View file

@ -1,83 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
// const opaqueMessage = `\
// Unfortunately, Void can't see the full error. However, you should be able to find more details by pressing ${getCmdKey()}+Shift+P, typing "Toggle Developer Tools", and looking at the console.\n
// This error often means you have an incorrect API key. If you're self-hosting your own server, it might mean your CORS headers are off, and you should make sure your server's response has the header "Access-Control-Allow-Origins" set to "*", or at least allows "vscode-file://vscode-app".`
// if ((error instanceof Error) && (error.cause + '').includes('TypeError: Failed to fetch')) {
// e = error as any
// e['Void Team'] = opaqueMessage
// }
type Details = {
message: string,
name: string,
stack: string | null,
cause: string | null,
code: string | null,
additional: Record<string, any>
}
// Get detailed error information
const getErrorDetails = (error: unknown) => {
let details: Details;
let e: Error & { [other: string]: undefined | any }
// If fetch() fails, it gives an opaque message. We add extra details to the error.
if (error instanceof Error) {
e = error
}
// sometimes error is an object but not an Error
else if (typeof error === 'object') {
e = new Error(`More details below.`, { cause: JSON.stringify(error) })
}
else {
e = new Error(String(error))
}
// console.log('error display', JSON.stringify(e))
const message = e.message && e.error ?
(e.message + ':\n' + e.error)
: e.message || e.error || JSON.stringify(error)
details = {
name: e.name || 'Error',
message: message,
stack: null, // e.stack is ignored because it's ugly and not very useful
cause: e.cause ? String(e.cause) : null,
code: e.code || null,
additional: {}
}
// Collect any additional properties from the e
for (let prop of Object.getOwnPropertyNames(e).filter((prop) => !Object.keys(details).includes(prop)))
details.additional[prop] = (e as any)[prop]
return details;
};
export const ErrorDisplay = ({
error,
onDismiss = null,
showDismiss = true,
message,
fullError,
onDismiss,
showDismiss,
}: {
error: Error | object | string,
message: string,
fullError: Error | null,
onDismiss: (() => void) | null,
showDismiss?: boolean,
className?: string
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const details = getErrorDetails(error);
const hasDetails = details.cause || Object.keys(details.additional).length > 0;
let details: string | null = null;
if (fullError === null) {
details = null
}
else if (typeof fullError === 'object') {
details = JSON.stringify(fullError, null, 2)
}
else if (typeof fullError === 'string') {
details = null
}
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 overflow-auto`}>
@ -87,16 +41,18 @@ export const ErrorDisplay = ({
<AlertCircle className="h-5 w-5 text-red-500 mt-0.5" />
<div className="flex-1">
<h3 className="font-semibold text-red-800">
{details.name}
{/* eg Error */}
Error
</h3>
<p className="text-red-700 mt-1">
{details.message}
{/* eg Something went wrong */}
{message}
</p>
</div>
</div>
<div className="flex gap-2">
{hasDetails && (
{details && (
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-red-600 hover:text-red-800 p-1 rounded"
@ -120,38 +76,12 @@ export const ErrorDisplay = ({
</div>
{/* Expandable Details */}
{isExpanded && hasDetails && (
{isExpanded && details && (
<div className="mt-4 space-y-3 border-t border-red-200 pt-3">
{details.code && (
<div>
<span className="font-semibold text-red-800">Error Code: </span>
<span className="text-red-700">{details.code}</span>
</div>
)}
{details.cause && (
<div>
<span className="font-semibold text-red-800">Cause: </span>
<span className="text-red-700">{details.cause}</span>
</div>
)}
{Object.keys(details.additional).length > 0 && (
<div>
<span className="font-semibold text-red-800">Additional Information:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{Object.keys(details.additional).map(key => `${key}:\n${details.additional[key]}`).join('\n')}
</pre>
</div>
)}
{/* {details.stack && (
<div>
<span className="font-semibold text-red-800">Stack Trace:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{details.stack}
</pre>
</div>
)} */}
<div>
<span className="font-semibold text-red-800">Full Error: </span>
<pre className="text-red-700">{details}</pre>
</div>
</div>
)}
</div>

View file

@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { useCallback, useEffect, useRef } from 'react'
import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { useConfigState, useService } from '../util/services.js'
import ErrorBoundary from './ErrorBoundary.js'
import { VoidSelectBox } from './inputs.js'
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
export const ModelSelectionOfFeature = ({ featureName }: { featureName: FeatureName }) => {
const voidConfigService = useService('configStateService')
const voidConfigState = useConfigState()
const modelOptions: { text: string, value: [string, string] }[] = []
modelOptions.push({ text: 'Select a Provider', value: ['MyProvider', 'MyModel'] })
for (const providerName of providerNames) {
const providerConfig = voidConfigState[providerName]
if (providerConfig.enabled !== 'true') continue
providerConfig.models?.forEach(model => {
modelOptions.push({ text: `${providerName} - ${model}`, value: [providerName, model] })
})
}
return <>
<h2>{featureName}</h2>
{
<VoidSelectBox
options={modelOptions}
onChangeSelection={(newVal) => { voidConfigService.setModelSelectionOfFeature(featureName, { providerName: newVal[0] as ProviderName, modelName: newVal[1] }) }}
// we are responsible for setting the initial state here
onCreateInstance={useCallback((instance: SelectBox) => {
const updateState = () => {
const settingsAtProvider = voidConfigService.state.modelSelectionOfFeature[featureName]
const index = modelOptions.findIndex(v => v.value[0] === settingsAtProvider?.providerName && v.value[1] === settingsAtProvider?.modelName)
if (index !== -1)
instance.select(index)
}
updateState()
const disposable = voidConfigService.onDidGetInitState(updateState)
return [disposable]
}, [voidConfigService, modelOptions, featureName])}
/>}
{/* <h1>Settings - {featureName}</h1> */}
{/* {models.map(([providerName, model], i) => <p key={i}>{providerName} - {model}</p>)} */}
</>
}
export const ModelSelectionSettings = () => {
return <>
{featureNames.map(featureName => <ModelSelectionOfFeature
key={featureName}
featureName={featureName}
/>)}
</>
}

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { useEffect, useState } from 'react'
import { mountFnGenerator } from '../util/mountFnGenerator.js'
@ -15,8 +15,9 @@ import { useSidebarState } from '../util/services.js';
import '../styles.css'
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { SidebarChat } from './SidebarChat.js';
import { SidebarModelSettings } from './SidebarModelSettings.js';
import { SidebarProviderSettings } from './SidebarProviderSettings.js';
import { ModelSelectionSettings } from './ModelSelectionSettings.js';
import { VoidProviderSettings } from './VoidProviderSettings.js';
import ErrorBoundary from './ErrorBoundary.js';
const Sidebar = () => {
const sidebarState = useSidebarState()
@ -33,17 +34,25 @@ const Sidebar = () => {
}}>clickme {tab}</span> */}
<div className={`mb-2 ${isHistoryOpen ? '' : 'hidden'}`}>
<SidebarThreadSelector />
<ErrorBoundary>
<SidebarThreadSelector />
</ErrorBoundary>
</div>
<div className={`${tab === 'chat' ? '' : 'hidden'}`}>
<SidebarChat />
<ErrorBoundary>
<SidebarChat />
</ErrorBoundary>
<ErrorBoundary>
<ModelSelectionSettings />
</ErrorBoundary>
</div>
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>
<SidebarModelSettings />
--------
<SidebarProviderSettings />
<ErrorBoundary>
<VoidProviderSettings />
</ErrorBoundary>
</div>
</div>

View file

@ -1,7 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState } from 'react';
@ -17,9 +18,9 @@ import { URI } from '../../../../../../../base/common/uri.js';
import { EndOfLinePreference } from '../../../../../../../editor/common/model.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from './ErrorDisplay.js';
import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { LLMMessageServiceParams, OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { getCmdKey } from '../../../getCmdKey.js'
import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { VoidInputBox } from './inputs.js';
@ -173,7 +174,9 @@ export const SelectedFiles = (
}
const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
const ChatBubble = ({ chatMessage }: {
chatMessage: ChatMessage
}) => {
const role = chatMessage.role
@ -203,7 +206,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
export const SidebarChat = () => {
const chatInputRef = useRef<HTMLTextAreaElement | null>(null)
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
const modelService = useService('modelService')
@ -213,11 +216,11 @@ export const SidebarChat = () => {
useEffect(() => {
const disposables: IDisposable[] = []
disposables.push(
sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() }),
sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() })
sidebarStateService.onDidFocusChat(() => { inputBoxRef.current?.focus() }),
sidebarStateService.onDidBlurChat(() => { inputBoxRef.current?.blur() })
)
return () => disposables.forEach(d => d.dispose())
}, [sidebarStateService, chatInputRef])
}, [sidebarStateService, inputBoxRef])
// config state
const voidConfigState = useConfigState()
@ -233,7 +236,7 @@ export const SidebarChat = () => {
const [isLoading, setIsLoading] = useState(false)
const latestRequestIdRef = useRef<string | null>(null)
const [latestError, setLatestError] = useState<Error | string | null>(null)
const [latestError, setLatestError] = useState<Parameters<OnError>[0] | null>(null)
const sendLLMMessageService = useService('sendLLMMessageService')
@ -242,7 +245,7 @@ export const SidebarChat = () => {
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
const isDisabled = !instructions
const formRef = useRef<HTMLFormElement | null>(null)
const inputBoxRef: React.MutableRefObject<HistoryInputBox | null> = useRef(null);
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
@ -283,6 +286,13 @@ export const SidebarChat = () => {
const currentThread = threadsStateService.getCurrentThread(threadsStateService.state) // the the instant state right now, don't wait for the React state
// send message to LLM
setIsLoading(true) // must come before message is sent so onError will work
setLatestError(null)
if (inputBoxRef.current) {
inputBoxRef.current.value = ''; // this triggers onDidChangeText
inputBoxRef.current.blur();
}
const object: LLMMessageServiceParams = {
logging: { loggingName: 'Chat' },
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content || '(null)' })),],
@ -296,8 +306,8 @@ export const SidebarChat = () => {
setMessageStream(null)
setIsLoading(false)
},
onError: ({ error }) => {
console.log('chat: running error', error)
onError: ({ message, fullError }) => {
console.log('chat: running error', message, fullError)
// add assistant's message to chat history, and clear selection
let content = messageStream ?? ''; // just use the current content
@ -307,23 +317,16 @@ export const SidebarChat = () => {
setMessageStream('')
setIsLoading(false)
setLatestError(error)
setLatestError({ message, fullError })
},
voidConfig: voidConfigState,
providerName: 'anthropic',
featureName: 'Ctrl+L',
}
const latestRequestId = sendLLMMessageService.sendLLMMessage(object)
latestRequestIdRef.current = latestRequestId
setIsLoading(true)
if (inputBoxRef.current) {
inputBoxRef.current.value = ''; // this triggers onDidChangeText
inputBoxRef.current.blur();
}
threadsStateService.setStaging([]) // clear staging
setLatestError(null)
}
@ -373,7 +376,8 @@ export const SidebarChat = () => {
{/* error message */}
{latestError === null ? null :
<ErrorDisplay
error={latestError}
message={latestError.message}
fullError={latestError.fullError}
onDismiss={() => { setLatestError(null) }}
/>
}
@ -395,7 +399,6 @@ export const SidebarChat = () => {
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
multiline={true}
initVal=''
/>
{/* submit/stop button */}

View file

@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { FeatureName, featureNames, providerNames } from '../../../../../../../platform/void/common/configTypes.js'
import { useConfigState } from '../util/services.js'
export const SidebarModelSettingsForFeature = ({ featureName }: { featureName: FeatureName }) => {
const voidConfigState = useConfigState()
const models: [string,string][] = []
for (const providerName of providerNames) {
const providerConfig = voidConfigState[providerName]
if (providerConfig.enabled === 'false') continue
providerConfig.models?.forEach(model => {
models.push([providerName,model])
})
}
return <>
<h1>Settings - {featureName}</h1>
{models.map(([providerName,model], i) => <span key={i}>{providerName} - {model}</span>)}
</>
}
export const SidebarModelSettings = () => {
return <>
{featureNames.map(featureName => <SidebarModelSettingsForFeature key={featureName} featureName={featureName} />)}
</>
}

View file

@ -1,71 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import React, { Fragment } from 'react'
import { displayInfoOfSettingName, ProviderName, providerNames } from '../../../../../../../platform/void/common/configTypes.js'
import { VoidCheckBox, VoidInputBox, VoidSelectBox } from './inputs.js'
import { useConfigState, useService } from '../util/services.js'
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
const voidConfigState = useConfigState()
const voidConfigService = useService('configStateService')
const { models, model, ...others } = voidConfigState[providerName]
return <>
<h1>{providerName}</h1>
{/* other settings (e.g. api key) */}
{Object.entries(others).map(([settingName, defaultVal], i) => {
const sName = settingName as keyof typeof others
const { title, type, placeholder } = displayInfoOfSettingName(providerName, sName)
return <Fragment key={i}>
<h2>{title}</h2>
{
type === 'boolean' ?
<VoidCheckBox
initVal={defaultVal === 'true'}
onChangeChecked={(newVal) => { voidConfigService.setState(providerName, sName, newVal ? 'true' : 'false') }}
label={settingName}
checkboxRef={{ current: null }}
/>
:
<VoidInputBox
initVal={defaultVal}
placeholder={placeholder}
onChangeText={(newVal) => { () => { voidConfigService.setState(providerName, sName, newVal) } }}
multiline={false}
inputBoxRef={{ current: null }}
/>}
</Fragment>
})}
<h2>{'Models'}</h2>
{models === null ?
<p>{'No models available.'}</p>
: <VoidSelectBox
initVal={models[0]}
options={models}
onChangeSelection={(newVal) => { () => { } }}
selectBoxRef={{ current: null }}
/>}
</>
}
export const SidebarProviderSettings = () => {
return <>
{providerNames.map(providerName =>
<SettingsForProvider key={providerName} providerName={providerName} />
)}
</>
}

View file

@ -1,150 +0,0 @@
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Glass Devtools, Inc. All rights reserved.
// * Void Editor additions licensed under the AGPLv3 License.
// *--------------------------------------------------------------------------------------------*/
// import React, { useCallback, useEffect, useRef, useState } from 'react';
// import { useConfigState, useService } from '../util/services.js';
// import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
// import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
// import { ConfigState, IVoidConfigStateService } from '../../../registerConfig.js';
// import { nonDefaultConfigFields, VoidConfigField } from '../../../../../../../platform/void/common/configTypes.js';
// const SettingOfFieldAndParam = ({ field, param, configState, configStateService }:
// { field: VoidConfigField; param: string; configState: ConfigState; configStateService: IVoidConfigStateService }) => {
// const { partialVoidConfig } = configState
// const { enumArr, defaultVal, description } = configStateService.voidConfigInfo[field][param]
// const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item
// const initValRef = useRef(val)
// const updateState = useCallback((newValue: string) => {
// configStateService.setField(field, param, newValue)
// }, [configStateService, field, param])
// const inputBoxRef = useRef<HistoryInputBox | null>(null);
// const selectBoxRef = useRef<SelectBox | null>(null);
// const forceState = useCallback((newValue: string) => {
// if (inputBoxRef.current) {
// inputBoxRef.current.value = newValue;
// }
// if (selectBoxRef.current) {
// selectBoxRef.current.select(enumArr?.indexOf(newValue) ?? 0);
// }
// // updateState is called automatically when the change happens
// }, [enumArr, updateState])
// const resetButton = <button
// disabled={val === defaultVal}
// title={val === defaultVal ? 'This is the default value.' : `Revert value to '${defaultVal}'?`}
// className='group btn btn-sm disabled:opacity-75 disabled:cursor-default'
// onClick={() => forceState(defaultVal)}
// >
// <svg
// className='size-5 group-disabled:stroke-current group-disabled:fill-current group-hover:stroke-red-600 group-hover:fill-red-600 duration-200'
// fill='currentColor' strokeWidth='0' viewBox='0 0 16 16' height='200px' width='200px' xmlns='http://www.w3.org/2000/svg'><path fillRule='evenodd' clipRule='evenodd' d='M3.5 2v3.5L4 6h3.5V5H4.979l.941-.941a3.552 3.552 0 1 1 5.023 5.023L5.746 14.28l.72.72 5.198-5.198A4.57 4.57 0 0 0 5.2 3.339l-.7.7V2h-1z'></path>
// </svg>
// </button>
// const inputElement = enumArr === undefined ?
// // string
// // (<VoidInputBox
// // onChangeText={updateState}
// // initVal={initValRef.current}
// // multiline={false}
// // placeholder=''
// // inputBoxRef={inputBoxRef}
// // />)
// <input
// className='input p-1 w-full'
// type='text'
// value={val}
// onChange={(e) => updateState(e.target.value)}
// />
// :
// // enum
// // (<VoidSelectBox
// // onChangeSelection={updateState}
// // initVal={initValRef.current}
// // options={enumArr}
// // selectBoxRef={selectBoxRef}
// // />)
// (<select
// className='dropdown p-1 w-full'
// value={val}
// onChange={(e) => updateState(e.target.value)}
// >
// {enumArr.map((option) => (
// <option key={option} value={option}>
// {option}
// </option>
// ))}
// </select>)
// return <div>
// <label className='hidden'>{param}</label>
// <span>{description}</span>
// <div className='flex items-center'>
// {inputElement}
// {resetButton}
// </div>
// </div>
// }
// export const SidebarSettings = () => {
// const configState = useConfigState()
// const configStateService = useService('configStateService')
// const { voidConfig } = configState
// const current_field = voidConfig.default['whichApi'] as VoidConfigField
// return (
// <div className='space-y-4 py-2 overflow-y-auto'>
// {/* choose the field */}
// <div className='outline-vscode-input-bg'>
// <SettingOfFieldAndParam
// configState={configState}
// configStateService={configStateService}
// field='default'
// param='whichApi'
// />
// <SettingOfFieldAndParam
// configState={configState}
// configStateService={configStateService}
// field='default'
// param='maxTokens'
// />
// </div>
// <hr />
// {/* render all fields, but hide the ones not visible for fast tab switching */}
// {nonDefaultConfigFields.map(field => {
// return <div
// key={field}
// className={`flex flex-col gap-y-2 ${field !== current_field ? 'hidden' : ''}`}
// >
// {Object.keys(configStateService.voidConfigInfo[field]).map((param) => (
// <SettingOfFieldAndParam
// key={param}
// configState={configState}
// configStateService={configStateService}
// field={field}
// param={param}
// />
// ))}
// </div>
// })}
// </div>
// )
// }

View file

@ -1,7 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React from "react";
import { useService, useThreadsState } from '../util/services.js';

View file

@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { titleOfProviderName, displayInfoOfSettingName, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { VoidInputBox } from './inputs.js'
import { useConfigState, useService } from '../util/services.js'
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
import ErrorBoundary from './ErrorBoundary.js'
const Setting = ({ providerName, settingName }: { providerName: ProviderName, settingName: any }) => {
const { title, type, placeholder } = displayInfoOfSettingName(providerName, settingName)
const voidConfigService = useService('configStateService')
return <><ErrorBoundary>
<label>{title}</label>
<VoidInputBox
placeholder={placeholder}
onChangeText={useCallback((newVal) => {
voidConfigService.setSettingOfProvider(providerName, settingName, newVal)
}, [voidConfigService, providerName, settingName])}
// we are responsible for setting the initial value here
onCreateInstance={useCallback((instance: InputBox) => {
const updateInstanceState = () => {
const settingsAtProvider = voidConfigService.state.settingsOfProvider[providerName];
// @ts-ignore
const stateVal = settingsAtProvider[settingName]
instance.value = stateVal
}
updateInstanceState()
const disposable = voidConfigService.onDidGetInitState(updateInstanceState)
return [disposable]
}, [voidConfigService, providerName, settingName])}
multiline={false}
/>
</ErrorBoundary></>
}
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
const voidConfigState = useConfigState()
const { models, ...others } = voidConfigState[providerName]
return <>
<h1 className='text-xl'>{titleOfProviderName(providerName)}</h1>
{/* settings besides models (e.g. api key) */}
{Object.keys(others).map((settingName, i) => {
return <Setting key={settingName} providerName={providerName} settingName={settingName} />
})}
</>
}
export const VoidProviderSettings = () => {
return <>
{providerNames.map(providerName =>
<SettingsForProvider key={providerName} providerName={providerName} />
)}
</>
}

View file

@ -1,29 +1,56 @@
import React, { useEffect, useRef } from 'react';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { useCallback, useEffect, useRef } from 'react';
import { useService } from '../util/services.js';
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultToggleStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles, defaultToggleStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox, unthemedSelectBoxStyles } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
import { Checkbox, Toggle } from '../../../../../../../base/browser/ui/toggle/toggle.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
// settingitem
export const VoidInputBox = ({ onChangeText, initVal, placeholder, inputBoxRef, multiline }: {
onChangeText: (value: string) => void;
placeholder: string;
inputBoxRef: React.MutableRefObject<InputBox | null>;
multiline: boolean;
initVal: string;
}) => {
const contextViewProvider = useService('contextViewService');
const containerRef = useRef<HTMLDivElement>(null);
export const WidgetComponent = <CtorParams extends any[], Instance>({ ctor, propsFn, dispose, onCreateInstance }
: {
ctor: { new(...params: CtorParams): Instance },
propsFn: (container: HTMLDivElement) => CtorParams,
onCreateInstance: (instance: Instance) => IDisposable[],
dispose: (instance: Instance) => void,
}
) => {
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!containerRef.current) return;
const instance = new ctor(...propsFn(containerRef.current!));
const disposables = onCreateInstance(instance);
return () => {
disposables.forEach(d => d.dispose());
dispose(instance)
}
}, [ctor, propsFn, dispose, onCreateInstance, containerRef])
// create and mount the HistoryInputBox
inputBoxRef.current = new InputBox(
containerRef.current,
return <div ref={containerRef} className='w-full' />
}
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline }: {
onChangeText: (value: string) => void;
onCreateInstance?: (instance: InputBox) => void | IDisposable[];
inputBoxRef?: { current: InputBox | null };
placeholder: string;
multiline: boolean;
}) => {
const contextViewProvider = useService('contextViewService');
return <WidgetComponent
ctor={InputBox}
propsFn={useCallback((container) => [
container,
contextViewProvider,
{
inputBoxStyles: {
@ -34,131 +61,184 @@ export const VoidInputBox = ({ onChangeText, initVal, placeholder, inputBoxRef,
flexibleHeight: multiline,
flexibleMaxHeight: 500,
flexibleWidth: false,
}
);
inputBoxRef.current.value = initVal;
inputBoxRef.current.onDidChange((newStr) => {
console.log('CHANGE TEXT on inputbox', newStr)
onChangeText(newStr)
})
// cleanup
return () => {
if (inputBoxRef.current) {
inputBoxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
inputBoxRef.current = null;
] as const, [contextViewProvider, placeholder, multiline])}
dispose={useCallback((instance: InputBox) => {
instance.dispose()
instance.element.remove()
}, [])}
onCreateInstance={useCallback((instance: InputBox) => {
const disposables: IDisposable[] = []
disposables.push(
instance.onDidChange((newText) => onChangeText(newText))
)
if (onCreateInstance) {
const ds = onCreateInstance(instance) ?? []
disposables.push(...ds)
}
};
}, [inputBoxRef, contextViewProvider, placeholder, multiline, initVal, onChangeText]);
return <div ref={containerRef} className="w-full" />;
// return <textarea
// ref={chatInputRef}
// placeholder={`Press ${getCmdKey()}+L to select.`}
// onChange={(e) => { setInstructions(e.target.value) }}
// className={`w-full p-2 leading-tight resize-none max-h-[50vh] overflow-auto bg-transparent border-none !outline-none`}
// rows={1}
// onInput={e => { e.currentTarget.style.height = 'auto'; e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px' }} // Adjust height dynamically
if (inputBoxRef)
inputBoxRef.current = instance;
return disposables
}, [onChangeText, onCreateInstance, inputBoxRef])
}
/>
};
export const VoidSelectBox = ({ onChangeSelection, initVal, selectBoxRef, options }: {
onChangeSelection: (value: string) => void;
initVal: string;
selectBoxRef: React.MutableRefObject<SelectBox | null>;
options: readonly string[];
export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectBoxRef, options }: {
onChangeSelection: (value: T) => void;
onCreateInstance?: ((instance: SelectBox) => void | IDisposable[]);
selectBoxRef?: React.MutableRefObject<SelectBox | null>;
options: readonly { text: string, value: T }[];
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const contextViewProvider = useService('contextViewService');
useEffect(() => {
if (!containerRef.current) return;
let containerRef = useRef<HTMLDivElement | null>(null);
const defaultIndex = options.indexOf(initVal);
return <WidgetComponent
ctor={SelectBox}
propsFn={useCallback((container) => {
containerRef.current = container
const defaultIndex = 0;
return [
options.map(opt => ({ text: opt.text })),
defaultIndex,
contextViewProvider,
defaultSelectBoxStyles
] as const;
}, [containerRef, options, contextViewProvider])}
selectBoxRef.current = new SelectBox(
options.map(opt => ({ text: opt })),
defaultIndex,
contextViewProvider,
unthemedSelectBoxStyles
);
dispose={useCallback((instance: SelectBox) => {
instance.dispose();
for (let child of containerRef.current?.childNodes ?? [])
containerRef.current?.removeChild(child)
}, [containerRef])}
selectBoxRef.current.render(containerRef.current);
onCreateInstance={useCallback((instance: SelectBox) => {
const disposables: IDisposable[] = []
selectBoxRef.current.onDidSelect(e => { onChangeSelection(e.selected); });
if (containerRef.current)
instance.render(containerRef.current)
// cleanup
return () => {
if (selectBoxRef.current) {
selectBoxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
disposables.push(
instance.onDidSelect(e => { onChangeSelection(options[e.index].value); })
)
if (onCreateInstance) {
const ds = onCreateInstance(instance) ?? []
disposables.push(...ds)
}
};
}, [options, initVal, onChangeSelection, contextViewProvider, selectBoxRef]);
if (selectBoxRef)
selectBoxRef.current = instance;
return <div ref={containerRef} className="w-full" />;
return disposables;
}, [containerRef, onChangeSelection, options, onCreateInstance, selectBoxRef])}
/>;
};
// export const VoidSelectBox = <T,>({ onChangeSelection, initVal, selectBoxRef, options }: {
// initVal: T;
// selectBoxRef: React.MutableRefObject<SelectBox | null>;
// options: readonly { text: string, value: T }[];
// onChangeSelection: (value: T) => void;
// }) => {
// const contextViewProvider = useService('contextViewService');
// const contextMenuProvider = useService('contextMenuService');
export const VoidCheckBox = ({ onChangeChecked, initVal, label, checkboxRef, }: {
onChangeChecked: (checked: boolean) => void;
initVal: boolean;
checkboxRef: React.MutableRefObject<Toggle | null>;
label: string;
}) => {
const containerRef = useRef<HTMLDivElement>(null);
// return <WidgetComponent
// ctor={DropdownMenu}
// propsFn={useCallback((container) => {
// return [
// container, {
// contextMenuProvider,
// actions: options.map(({ text, value }, i) => ({
// id: i + '',
// label: text,
// tooltip: text,
// class: undefined,
// enabled: true,
// run: () => {
// onChangeSelection(value);
// },
// }))
useEffect(() => {
if (!containerRef.current) return;
// }] as const;
// }, [options, initVal, contextViewProvider])}
// Create and mount the Checkbox using VSCode's implementation
checkboxRef.current = new Toggle({
title: label,
isChecked: initVal,
...defaultToggleStyles
});
// dispose={useCallback((instance: DropdownMenu) => {
// instance.dispose();
// // instance.element.remove()
// }, [])}
containerRef.current.appendChild(checkboxRef.current.domNode);
// onCreateInstance={useCallback((instance: DropdownMenu) => {
// return []
// }, [])}
checkboxRef.current.onChange(checked => {
console.log('CHANGE checked state on checkbox', checked);
onChangeChecked(checked);
});
// cleanup
return () => {
if (checkboxRef.current) {
checkboxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
checkboxRef.current = null;
}
};
}, [checkboxRef, label, initVal, onChangeChecked]);
return <div ref={containerRef} className="w-full" />;
};
// />;
// };
// export const VoidCheckBox = ({ onChangeChecked, initVal, label, checkboxRef, }: {
// onChangeChecked: (checked: boolean) => void;
// initVal: boolean;
// checkboxRef: React.MutableRefObject<ObjectSettingCheckboxWidget | null>;
// label: string;
// }) => {
// const containerRef = useRef<HTMLDivElement>(null);
// const themeService = useService('themeService');
// const contextViewService = useService('contextViewService');
// const hoverService = useService('hoverService');
// useEffect(() => {
// if (!containerRef.current) return;
// // Create and mount the Checkbox using VSCode's implementation
// checkboxRef.current = new ObjectSettingCheckboxWidget(
// containerRef.current,
// themeService,
// contextViewService,
// hoverService,
// );
// checkboxRef.current.setValue([{
// key: { type: 'string', data: label },
// value: { type: 'boolean', data: initVal },
// removable: false,
// resetable: true,
// }])
// checkboxRef.current.onDidChangeList((list) => {
// onChangeChecked(!!list);
// })
// // cleanup
// return () => {
// if (checkboxRef.current) {
// checkboxRef.current.dispose();
// if (containerRef.current) {
// while (containerRef.current.firstChild) {
// containerRef.current.removeChild(containerRef.current.firstChild);
// }
// }
// checkboxRef.current = null;
// }
// };
// }, [checkboxRef, label, initVal, onChangeChecked]);
// return <div ref={containerRef} className="w-full" />;
// };

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -1,161 +0,0 @@
import React, { useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
import { getCmdKey } from '../../../getCmdKey.js';
// const opaqueMessage = `\
// Unfortunately, Void can't see the full error. However, you should be able to find more details by pressing ${getCmdKey()}+Shift+P, typing "Toggle Developer Tools", and looking at the console.\n
// This error often means you have an incorrect API key. If you're self-hosting your own server, it might mean your CORS headers are off, and you should make sure your server's response has the header "Access-Control-Allow-Origins" set to "*", or at least allows "vscode-file://vscode-app".`
// if ((error instanceof Error) && (error.cause + '').includes('TypeError: Failed to fetch')) {
// e = error as any
// e['Void Team'] = opaqueMessage
// }
type Details = {
message: string,
name: string,
stack: string | null,
cause: string | null,
code: string | null,
additional: Record<string, any>
}
// Get detailed error information
const getErrorDetails = (error: unknown) => {
let details: Details;
let e: Error & { [other: string]: undefined | any }
// If fetch() fails, it gives an opaque message. We add extra details to the error.
if (error instanceof Error) {
e = error
}
// sometimes error is an object but not an Error
else if (typeof error === 'object') {
e = new Error(`The server didn't give a very useful error message. More details below.`, { cause: JSON.stringify(error) })
}
else {
e = new Error(String(error))
}
// console.log('error display', JSON.stringify(e))
const message = e.message && e.error ?
(e.message + ':\n' + e.error)
: e.message || e.error || JSON.stringify(error)
details = {
name: e.name || 'Error',
message: message,
stack: null, // e.stack is ignored because it's ugly and not very useful
cause: e.cause ? String(e.cause) : null,
code: e.code || null,
additional: {}
}
// Collect any additional properties from the e
for (let prop of Object.getOwnPropertyNames(e).filter((prop) => !Object.keys(details).includes(prop)))
details.additional[prop] = (e as any)[prop]
return details;
};
export const ErrorDisplay = ({
error,
onDismiss = null,
showDismiss = true,
className = ''
}: {
error: Error | object | string,
onDismiss: (() => void) | null,
showDismiss?: boolean,
className?: string
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const details = getErrorDetails(error);
const hasDetails = details.cause || Object.keys(details.additional).length > 0;
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 ${className}`}>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex gap-3">
<AlertCircle className="h-5 w-5 text-red-500 mt-0.5" />
<div className="flex-1">
<h3 className="font-semibold text-red-800">
{details.name}
</h3>
<p className="text-red-700 mt-1">
{details.message}
</p>
</div>
</div>
<div className="flex gap-2">
{hasDetails && (
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-red-600 hover:text-red-800 p-1 rounded"
>
{isExpanded ? (
<ChevronUp className="h-5 w-5" />
) : (
<ChevronDown className="h-5 w-5" />
)}
</button>
)}
{showDismiss && onDismiss && (
<button
onClick={onDismiss}
className="text-red-600 hover:text-red-800 p-1 rounded"
>
<X className="h-5 w-5" />
</button>
)}
</div>
</div>
{/* Expandable Details */}
{isExpanded && hasDetails && (
<div className="mt-4 space-y-3 border-t border-red-200 pt-3">
{details.code && (
<div>
<span className="font-semibold text-red-800">Error Code: </span>
<span className="text-red-700">{details.code}</span>
</div>
)}
{details.cause && (
<div>
<span className="font-semibold text-red-800">Cause: </span>
<span className="text-red-700">{details.cause}</span>
</div>
)}
{Object.keys(details.additional).length > 0 && (
<div>
<span className="font-semibold text-red-800">Additional Information:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{Object.keys(details.additional).map(key => `${key}:\n${details.additional[key]}`).join('\n')}
</pre>
</div>
)}
{/* {details.stack && (
<div>
<span className="font-semibold text-red-800">Stack Trace:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{details.stack}
</pre>
</div>
)} */}
</div>
)}
</div>
);
};

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { diffLines, Change } from 'diff';
export { diffLines, Change }

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import React, { useEffect, useState } from 'react';
import * as ReactDOM from 'react-dom/client'
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';

View file

@ -1,7 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { useState, useEffect } from 'react'
import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js'
import { ThreadsState } from '../../../registerThreads.js'
import { VoidProviderState } from '../../../../../../../platform/void/common/configTypes.js'
import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidConfigTypes.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@ -10,13 +15,13 @@ let services: ReactServicesType
// even if React hasn't mounted yet, these variables are always updated to the latest state:
let sidebarState: VoidSidebarState
let configState: VoidProviderState
let threadsState: ThreadsState
let settingsOfProvider: SettingsOfProvider
// React listens by adding a setState function to these:
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
const configStateListeners: Set<(s: VoidProviderState) => void> = new Set()
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
const settingsOfProviderListeners: Set<(s: SettingsOfProvider) => void> = new Set()
// must call this before you can use any of the hooks below
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
@ -25,7 +30,7 @@ let wasCalled = false
export const _registerServices = (services_: ReactServicesType) => {
if (wasCalled) console.error(`void _registerServices was called again! It should only be called once.`)
if (wasCalled) console.error(`⚠️ Void _registerServices was called again! It should only be called once.`)
wasCalled = true
services = services_
@ -37,11 +42,6 @@ export const _registerServices = (services_: ReactServicesType) => {
sidebarStateListeners.forEach(l => l(sidebarState))
})
configState = configStateService.state
configStateService.onDidChangeState(() => {
configState = configStateService.state
configStateListeners.forEach(l => l(configState))
})
threadsState = threadsStateService.state
threadsStateService.onDidChangeCurrentThread(() => {
@ -49,15 +49,20 @@ export const _registerServices = (services_: ReactServicesType) => {
threadsStateListeners.forEach(l => l(threadsState))
})
settingsOfProvider = configStateService.state.settingsOfProvider
configStateService.onDidChangeState(() => {
settingsOfProvider = configStateService.state.settingsOfProvider
settingsOfProviderListeners.forEach(l => l(settingsOfProvider))
})
}
// -- services --
export const useService = <T extends keyof ReactServicesType,>(serviceName: T) => {
export const useService = <T extends keyof ReactServicesType,>(serviceName: T): ReactServicesType[T] => {
if (services === null) {
throw new Error('useAccessor must be used within an AccessorProvider')
}
return services[serviceName] as ReactServicesType[T]
return services[serviceName]
}
// -- state of services --
@ -73,11 +78,11 @@ export const useSidebarState = () => {
}
export const useConfigState = () => {
const [s, ss] = useState(configState)
const [s, ss] = useState(settingsOfProvider)
useEffect(() => {
ss(configState)
configStateListeners.add(ss)
return () => { configStateListeners.delete(ss) }
ss(settingsOfProvider)
settingsOfProviderListeners.add(ss)
return () => { settingsOfProviderListeners.delete(ss) }
}, [ss])
return s
}

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file

View file

@ -1,4 +1,9 @@
{
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": false,

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { defineConfig } from 'tsup'
export default defineConfig({

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
@ -12,7 +12,6 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { CodeStagingSelection, IThreadHistoryService } from './registerThreads.js';
// import { IVoidConfigService } from './registerSettings.js';
// import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';

View file

@ -1,13 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IVoidConfigStateService } from './registerConfig.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { Position } from '../../../../editor/common/core/position.js';
import { InlineCompletion, InlineCompletionContext } from '../../../../editor/common/languages.js';
@ -516,7 +515,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
const disabled = true
const testMode = false
if (disabled) { return []; }
if (disabled) return [];
const docUriStr = model.uri.toString();
@ -671,13 +670,13 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
resolve(newAutocompletion.insertText)
},
onError: ({ error }) => {
onError: ({ message }) => {
newAutocompletion.endTime = Date.now()
newAutocompletion.status = 'error'
reject(error)
reject(message)
},
providerName: 'anthropic',
voidConfig: this._voidConfigStateService.state,
featureName: 'Autocomplete',
range: { startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: position.lineNumber, endColumn: position.column },
})
newAutocompletion.requestId = requestId
@ -714,7 +713,6 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
constructor(
@ILanguageFeaturesService private _langFeatureService: ILanguageFeaturesService,
@IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
@ISendLLMMessageService private readonly _sendLLMMessageService: ISendLLMMessageService,
@IEditorService private readonly _editorService: IEditorService,
@IModelService private readonly _modelService: IModelService,

View file

@ -1,106 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { deepClone } from '../../../../base/common/objects.js';
import { IEncryptionService } from '../../../../platform/encryption/common/encryptionService.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { defaultVoidProviderState, ProviderName, VoidProviderState } from '../../../../platform/void/common/configTypes.js';
const VOID_CONFIG_KEY = 'void.config'
type SetStateFn = <K extends ProviderName>(
providerName: K,
option: keyof VoidProviderState[K],
newVal: string
) => Promise<void>;
export interface IVoidConfigStateService {
readonly _serviceBrand: undefined;
readonly state: VoidProviderState;
onDidChangeState: Event<void>;
setState: SetStateFn;
}
export const IVoidConfigStateService = createDecorator<IVoidConfigStateService>('VoidConfigStateService');
class VoidConfigStateService extends Disposable implements IVoidConfigStateService {
_serviceBrand: undefined;
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
state: VoidProviderState;
// readonly voidConfigInfo: VoidConfigInfo = voidConfigInfo; // just putting this here for simplicity, it's static though
get _defaultState() {
return deepClone(defaultVoidProviderState)
}
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IEncryptionService private readonly _encryptionService: IEncryptionService,
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
) {
super()
// at the start, we haven't read the partial config yet, but we need to set state to something, just treat partialVoidConfig like it's empty
this.state = this._defaultState
// read and update the actual state immediately
this._readVoidConfigState().then(voidConfigState => {
this._setState(voidConfigState)
})
}
private async _readVoidConfigState(): Promise<VoidProviderState> {
const encryptedPartialConfig = this._storageService.get(VOID_CONFIG_KEY, StorageScope.APPLICATION)
if (!encryptedPartialConfig)
return this._defaultState
const voidConfigStateStr = await this._encryptionService.decrypt(encryptedPartialConfig)
return JSON.parse(voidConfigStateStr)
}
private async _storeVoidConfigState(voidConfigState: VoidProviderState) {
const encryptedVoidConfigStr = await this._encryptionService.encrypt(JSON.stringify(voidConfigState))
this._storageService.store(VOID_CONFIG_KEY, encryptedVoidConfigStr, StorageScope.APPLICATION, StorageTarget.USER)
}
// Set field on PartialVoidConfig
setState: SetStateFn = async (providerName, option, newVal) => {
const newState: VoidProviderState = {
...this.state,
[providerName]: {
...this.state[providerName],
[option]: newVal,
}
}
await this._storeVoidConfigState(newState)
this._setState(newState)
}
// internal function to update state, should be called every time state changes
private async _setState(voidConfigState: VoidProviderState) {
this.state = voidConfigState
this._onDidChangeState.fire()
}
}
registerSingleton(IVoidConfigStateService, VoidConfigStateService, InstantiationType.Eager);

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
// import { throttle } from '../../../../base/common/decorators.js';
import { IVoidConfigStateService } from './registerConfig.js';
// import { IVoidConfigStateService } from './registerConfig.js';
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
import { ComputedDiff, findDiffs } from './findDiffs.js';
import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js';
@ -28,9 +28,8 @@ import { ILanguageService } from '../../../../editor/common/languages/language.j
import * as dom from '../../../../base/browser/dom.js';
import { Widget } from '../../../../base/browser/ui/widget.js';
import { URI } from '../../../../base/common/uri.js';
import { LLMMessageServiceParams } from '../../../../platform/void/common/llmMessageTypes.js';
import { LLMFeatureSelection, LLMMessageServiceParams } from '../../../../platform/void/common/llmMessageTypes.js';
import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js';
import { ProviderName } from '../../../../platform/void/common/configTypes.js';
// gets converted to --vscode-void-greenBG, see void.css
@ -104,25 +103,17 @@ type HistorySnapshot = {
entireFileCode: string;
} &
({
type: 'ctrl+k';
type: 'Ctrl+K';
ctrlKText: string;
} | {
type: 'ctrl+l';
type: 'Ctrl+L';
})
type StartStreamingOptions = {
type: 'ctrl+k',
providerName: ProviderName,
range: IRange
} | {
type: 'ctrl+l',
providerName: ProviderName
}
export interface IInlineDiffsService {
readonly _serviceBrand: undefined;
startStreaming(params: StartStreamingOptions, str: string): void;
startStreaming(params: LLMFeatureSelection, str: string): void;
}
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffAreasService');
@ -153,7 +144,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
constructor(
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
@IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
// @IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IModelService private readonly _modelService: IModelService,
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
@ -379,7 +370,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
return {
snapshottedDiffAreaOfId,
entireFileCode: this._readURI(uri) ?? '', // the whole file's code
type: 'ctrl+l',
type: 'Ctrl+L',
}
}
@ -646,7 +637,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
private async _initializeStream(uri: URI, diffRepr: string, providerName: ProviderName) {
private async _initializeStream(opts: LLMFeatureSelection, diffRepr: string, uri: URI,) {
// diff area begin and end line
const numLines = this._getNumLines(uri)
@ -698,7 +689,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
this.diffAreaOfId[diffArea.diffareaid] = diffArea
// actually call the LLM
const voidConfigState = this._voidConfigStateService.state
const promptContent = `\
ORIGINAL_CODE
\`\`\`
@ -715,29 +705,6 @@ Please finish writing the new file by applying the diff to the original file. Re
`
// CTRL+K prompt:
// const promptContent = `Here is the user's original selection:
// \`\`\`
// <MID>${selection}</MID>
// \`\`\`
// The user wants to apply the following instructions to the selection:
// ${instructions}
// Please rewrite the selection following the user's instructions.
// Instructions to follow:
// 1. Follow the user's instructions
// 2. You may ONLY CHANGE the selection, and nothing else in the file
// 3. Make sure all brackets in the new selection are balanced the same was as in the original selection
// 3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
// Complete the following:
// \`\`\`
// <PRE>${prefix}</PRE>
// <SUF>${suffix}</SUF>
// <MID>`;
await new Promise<void>((resolve, reject) => {
let streamRequestId: string | null = null
@ -770,8 +737,7 @@ Please finish writing the new file by applying the diff to the original file. Re
diffArea._sweepState = { isStreaming: false, line: null }
resolve();
},
voidConfig: voidConfigState,
providerName,
...opts
}
streamRequestId = this._sendLLMMessageService.sendLLMMessage(object)
@ -786,7 +752,7 @@ Please finish writing the new file by applying the diff to the original file. Re
async startStreaming(params: StartStreamingOptions, userMessage: string) {
async startStreaming(opts: LLMFeatureSelection, userMessage: string) {
const editor = this._editorService.getActiveCodeEditor()
if (!editor) return
@ -798,7 +764,7 @@ Please finish writing the new file by applying the diff to the original file. Re
// TODO deselect user's cursor
this._initializeStream(uri, userMessage, params.providerName)
this._initializeStream(opts, userMessage, uri)
}

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Registry } from '../../../../platform/registry/common/platform.js';
@ -42,7 +42,7 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import mountFn from './react/out/sidebar-tsx/Sidebar.js';
import { IVoidConfigStateService } from './registerConfig.js';
import { IVoidConfigStateService } from '../../../../platform/void/common/voidConfigService.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IInlineDiffsService } from './registerInlineDiffs.js';
import { IModelService } from '../../../../editor/common/services/model.js';
@ -70,6 +70,9 @@ export type ReactServicesType = {
sendLLMMessageService: ISendLLMMessageService;
clipboardService: IClipboardService;
themeService: IThemeService,
hoverService: IHoverService,
contextViewService: IContextViewService;
contextMenuService: IContextMenuService;
}
@ -113,6 +116,8 @@ class VoidSidebarViewPane extends ViewPane {
inlineDiffService: accessor.get(IInlineDiffsService),
sendLLMMessageService: accessor.get(ISendLLMMessageService),
clipboardService: accessor.get(IClipboardService),
themeService: accessor.get(IThemeService),
hoverService: accessor.get(IHoverService),
contextViewService: accessor.get(IContextViewService),
contextMenuService: accessor.get(IContextMenuService),
}

View file

@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';

View file

@ -1,14 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
// register keybinds
import './registerActions.js'
// register Settings
import './registerConfig.js' // TODO move this to platform
// register inline diffs
import './registerInlineDiffs.js'

View file

@ -817,7 +817,7 @@ export class GettingStartedPage extends EditorPane {
const header = $('.header', {},
$('h1.product-name.caption', {}, this.productService.nameLong),
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "The open source AI code editor."))
);
const leftColumn = $('.categories-column.categories-column-left', {},);

View file

@ -248,30 +248,30 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
type: 'svg', altText: 'Language extensions', path: 'languages.svg'
},
},
{
id: 'settings',
title: localize('gettingStarted.settings.title', "Tune your settings"),
description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')),
media: {
type: 'svg', altText: 'VS Code Settings', path: 'settings.svg'
},
},
{
id: 'settingsSync',
title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"),
description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')),
when: 'syncStatus != uninitialized',
completionEvents: ['onEvent:sync-enabled'],
media: {
type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg'
},
},
{
id: 'commandPaletteTask',
title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "),
description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')),
media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' },
},
// {
// id: 'settings',
// title: localize('gettingStarted.settings.title', "Tune your settings"),
// description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')),
// media: {
// type: 'svg', altText: 'VS Code Settings', path: 'settings.svg'
// },
// },
// {
// id: 'settingsSync',
// title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"),
// description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')),
// when: 'syncStatus != uninitialized',
// completionEvents: ['onEvent:sync-enabled'],
// media: {
// type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg'
// },
// },
// {
// id: 'commandPaletteTask',
// title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "),
// description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')),
// media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' },
// },
{
id: 'pickAFolderTask-Mac',
title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"),
@ -299,12 +299,12 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
type: 'svg', altText: 'Go to file in quick search.', path: 'search.svg'
}
},
{
id: 'videoTutorial',
title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"),
description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')),
media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' },
}
// {
// id: 'videoTutorial',
// title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"),
// description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')),
// media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' },
// }
]
}
},

View file

@ -18,6 +18,7 @@ import './browser/workbench.contribution.js';
// Void added this:
import './contrib/void/browser/void.contribution.js';
import '../platform/void/browser/llmMessageService.js';
import '../platform/void/common/voidConfigService.js';
//#endregion