diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b31fe30f..310fe556 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ If you ran `npm run watch`, the build is done when you see something like this: -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 Ctrl+Shift+P 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 diff --git a/extensions/typescript-language-features/web/src/serverHost.ts b/extensions/typescript-language-features/web/src/serverHost.ts index dedec859..9a61a8f8 100644 --- a/extensions/typescript-language-features/web/src/serverHost.ts +++ b/extensions/typescript-language-features/web/src/serverHost.ts @@ -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, diff --git a/package-lock.json b/package-lock.json index 8cd1f77f..82df8eed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/package.json b/package.json index 8bcd3416..409cf26c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index e86dca1d..afb593cc 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -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); diff --git a/src/vs/platform/void/browser/llmMessageService.ts b/src/vs/platform/void/browser/llmMessageService.ts index 61f51ed8..838cf7c0 100644 --- a/src/vs/platform/void/browser/llmMessageService.ts +++ b/src/vs/platform/void/browser/llmMessageService.ts @@ -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('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 = {} + 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(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service, not needed here + // const service = ProxyChannel.toService(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 = this.channel.listen('onText') + const onFinalMessageEvent: Event = this.channel.listen('onFinalMessage') + const onErrorEvent: Event = 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 = 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 = 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 = 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] } } diff --git a/src/vs/platform/void/browser/metricsService.ts b/src/vs/platform/void/browser/metricsService.ts index dfd7c898..47043c1c 100644 --- a/src/vs/platform/void/browser/metricsService.ts +++ b/src/vs/platform/void/browser/metricsService.ts @@ -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'; diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index b38f3b5c..a9bbb592 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -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 & { requestId: string } +export type BlockedProxyParams = 'onText' | 'onFinalMessage' | 'onError' | 'abortRef' +export type ProxyLLMMessageParams = Omit & { requestId: string } export type ProxyOnTextPayload = Parameters[0] & { requestId: string } export type ProxyOnFinalMessagePayload = Parameters[0] & { requestId: string } export type ProxyOnErrorPayload = Parameters[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 diff --git a/src/vs/platform/void/common/metricsService.ts b/src/vs/platform/void/common/metricsService.ts index 2459214e..45c79c2c 100644 --- a/src/vs/platform/void/common/metricsService.ts +++ b/src/vs/platform/void/common/metricsService.ts @@ -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 { diff --git a/src/vs/platform/void/common/voidConfigService.ts b/src/vs/platform/void/common/voidConfigService.ts new file mode 100644 index 00000000..8fc3aecd --- /dev/null +++ b/src/vs/platform/void/common/voidConfigService.ts @@ -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 = ( + providerName: K, + option: keyof SettingsOfProvider[K], + newVal: string +) => Promise; + +type SetModelSelectionOfFeature = ( + featureName: K, + newVal: ModelSelectionOfFeature[K], +) => Promise; + + +type VoidConfigState = { + settingsOfProvider: SettingsOfProvider; // optionsOfProvider + modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature +} + +export interface IVoidConfigStateService { + readonly _serviceBrand: undefined; + readonly state: VoidConfigState; + onDidChangeState: Event; + onDidGetInitState: Event; + 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('VoidConfigStateService'); +class VoidConfigStateService extends Disposable implements IVoidConfigStateService { + _serviceBrand: undefined; + + private readonly _onDidChangeState = new Emitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes + + private readonly _onDidGetInitState = new Emitter(); + readonly onDidGetInitState: Event = 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 { + 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); diff --git a/src/vs/platform/void/common/configTypes.ts b/src/vs/platform/void/common/voidConfigTypes.ts similarity index 76% rename from src/vs/platform/void/common/configTypes.ts rename to src/vs/platform/void/common/voidConfigTypes.ts index 2b698fb0..6d7dcc47 100644 --- a/src/vs/platform/void/common/configTypes.ts +++ b/src/vs/platform/void/common/voidConfigTypes.ts @@ -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 extends T ? keyof T : never; -type ProviderSettingName = UnionOfKeys +export type SettingName = UnionOfKeys @@ -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 diff --git a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts index c79413d6..b3610efb 100644 --- a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts +++ b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts @@ -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 } }) diff --git a/src/vs/platform/void/electron-main/llmMessage/gemini.ts b/src/vs/platform/void/electron-main/llmMessage/gemini.ts index dfd37e94..d68879cb 100644 --- a/src/vs/platform/void/electron-main/llmMessage/gemini.ts +++ b/src/vs/platform/void/electron-main/llmMessage/gemini.ts @@ -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 }); } }) } diff --git a/src/vs/platform/void/electron-main/llmMessage/greptile.ts b/src/vs/platform/void/electron-main/llmMessage/greptile.ts index e0c993ac..21ac3f71 100644 --- a/src/vs/platform/void/electron-main/llmMessage/greptile.ts +++ b/src/vs/platform/void/electron-main/llmMessage/greptile.ts @@ -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', diff --git a/src/vs/platform/void/electron-main/llmMessage/groq.ts b/src/vs/platform/void/electron-main/llmMessage/groq.ts index 6506c4ab..cbeb8669 100644 --- a/src/vs/platform/void/electron-main/llmMessage/groq.ts +++ b/src/vs/platform/void/electron-main/llmMessage/groq.ts @@ -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 }); }) diff --git a/src/vs/platform/void/electron-main/llmMessage/ollama.ts b/src/vs/platform/void/electron-main/llmMessage/ollama.ts index 92754949..b631aad2 100644 --- a/src/vs/platform/void/electron-main/llmMessage/ollama.ts +++ b/src/vs/platform/void/electron-main/llmMessage/ollama.ts @@ -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 }) }) }; diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/platform/void/electron-main/llmMessage/openai.ts index 7f59e5ac..0e7e21b0 100644 --- a/src/vs/platform/void/electron-main/llmMessage/openai.ts +++ b/src/vs/platform/void/electron-main/llmMessage/openai.ts @@ -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 }); } }) diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index e6daf1cb..b0823547 100644 --- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -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 } diff --git a/src/vs/platform/void/electron-main/llmMessage/util.ts b/src/vs/platform/void/electron-main/llmMessage/util.ts index 00517168..988e4706 100644 --- a/src/vs/platform/void/electron-main/llmMessage/util.ts +++ b/src/vs/platform/void/electron-main/llmMessage/util.ts @@ -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 diff --git a/src/vs/platform/void/electron-main/llmMessageChannel.ts b/src/vs/platform/void/electron-main/llmMessageChannel.ts index f4646f8b..203eebd6 100644 --- a/src/vs/platform/void/electron-main/llmMessageChannel.ts +++ b/src/vs/platform/void/electron-main/llmMessageChannel.ts @@ -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 = {} + // 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 { + listen(_: unknown, event: BlockedProxyParams): Event { 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); diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts index 42849bef..aaaf8119 100644 --- a/src/vs/platform/void/electron-main/metricsMainService.ts +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -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) } } diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 0b426a16..64925e46 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -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 }); - // }); } diff --git a/src/vs/workbench/contrib/void/browser/findDiffs.ts b/src/vs/workbench/contrib/void/browser/findDiffs.ts index fac7f3db..32b893e4 100644 --- a/src/vs/workbench/contrib/void/browser/findDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/findDiffs.ts @@ -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' diff --git a/src/vs/workbench/contrib/void/browser/getCmdKey.ts b/src/vs/workbench/contrib/void/browser/getCmdKey.ts index 76344138..c1961814 100644 --- a/src/vs/workbench/contrib/void/browser/getCmdKey.ts +++ b/src/vs/workbench/contrib/void/browser/getCmdKey.ts @@ -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'; diff --git a/src/vs/workbench/contrib/void/browser/media/void.css b/src/vs/workbench/contrib/void/browser/media/void.css index 90c274ba..cf317680 100644 --- a/src/vs/workbench/contrib/void/browser/media/void.css +++ b/src/vs/workbench/contrib/void/browser/media/void.css @@ -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); } diff --git a/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts b/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts index 45be6d8d..17372ec9 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts @@ -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'; diff --git a/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts b/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts index 2faa8909..157f4292 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts @@ -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: +// \`\`\` +// ${selection} +// \`\`\` + +// 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: +// \`\`\` +//
${prefix}
+// ${suffix} +// `; + + + 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\`. diff --git a/src/vs/workbench/contrib/void/browser/react/build.js b/src/vs/workbench/contrib/void/browser/react/build.js index e15fdae6..9fb5fac6 100755 --- a/src/vs/workbench/contrib/void/browser/react/build.js +++ b/src/vs/workbench/contrib/void/browser/react/build.js @@ -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 diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx index 4773d645..b2c83e66 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx @@ -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"; diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 4b0533cf..0977a18a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -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 diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx new file mode 100644 index 00000000..a095f6fd --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorBoundary.tsx @@ -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 { + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null + }; + } + + static getDerivedStateFromError(error: Error): Partial { + 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 ( + + ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx index af205dd3..b48c9668 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx @@ -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 -} - -// 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 (
@@ -87,16 +41,18 @@ export const ErrorDisplay = ({

- {details.name} + {/* eg Error */} + Error

- {details.message} + {/* eg Something went wrong */} + {message}

- {hasDetails && ( + {details && (
{/* Expandable Details */} - {isExpanded && hasDetails && ( + {isExpanded && details && (
- {details.code && ( -
- Error Code: - {details.code} -
- )} - - {details.cause && ( -
- Cause: - {details.cause} -
- )} - - {Object.keys(details.additional).length > 0 && ( -
- Additional Information: -
-								{Object.keys(details.additional).map(key => `${key}:\n${details.additional[key]}`).join('\n')}
-							
-
- )} - {/* {details.stack && ( -
- Stack Trace: -
-								{details.stack}
-							
-
- )} */} +
+ Full Error: +
{details}
+
)} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx new file mode 100644 index 00000000..ef84f752 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx @@ -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 <> +

{featureName}

+ { + { 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])} + />} + + {/*

Settings - {featureName}

*/} + {/* {models.map(([providerName, model], i) =>

{providerName} - {model}

)} */} + +} + +export const ModelSelectionSettings = () => { + return <> + {featureNames.map(featureName => )} + +} + diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index 5d43ad46..d605e2d8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -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} */}
- + + +
- + + + + + + +
- - -------- - + + +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 509985f3..b45c0e2f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -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(null) + const inputBoxRef: React.MutableRefObject = 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(null) - const [latestError, setLatestError] = useState(null) + const [latestError, setLatestError] = useState[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(null) - const inputBoxRef: React.MutableRefObject = useRef(null); + const onSubmit = async (e: FormEvent) => { @@ -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 : { setLatestError(null) }} /> } @@ -395,7 +399,6 @@ export const SidebarChat = () => { onChangeText={onChangeText} inputBoxRef={inputBoxRef} multiline={true} - initVal='' /> {/* submit/stop button */} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx deleted file mode 100644 index 8cfb6321..00000000 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx +++ /dev/null @@ -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 <> -

Settings - {featureName}

- {models.map(([providerName,model], i) => {providerName} - {model})} - -} - -export const SidebarModelSettings = () => { - return <> - {featureNames.map(featureName => )} - -} - diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx deleted file mode 100644 index d82626a0..00000000 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx +++ /dev/null @@ -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 <> -

{providerName}

- - {/* 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 -

{title}

- { - type === 'boolean' ? - { voidConfigService.setState(providerName, sName, newVal ? 'true' : 'false') }} - label={settingName} - checkboxRef={{ current: null }} - /> - : - { () => { voidConfigService.setState(providerName, sName, newVal) } }} - multiline={false} - inputBoxRef={{ current: null }} - />} -
- })} - -

{'Models'}

- {models === null ? -

{'No models available.'}

- : { () => { } }} - selectBoxRef={{ current: null }} - />} - - - - -} - - -export const SidebarProviderSettings = () => { - - return <> - {providerNames.map(providerName => - - )} - - - -} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx deleted file mode 100644 index d5b23aeb..00000000 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx +++ /dev/null @@ -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(null); -// const selectBoxRef = useRef(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 = - - -// const inputElement = enumArr === undefined ? -// // string -// // () -// updateState(e.target.value)} -// /> -// : -// // enum -// // () -// () - -// return
-// -// {description} -//
-// {inputElement} -// {resetButton} -//
-//
-// } - - -// export const SidebarSettings = () => { - -// const configState = useConfigState() -// const configStateService = useService('configStateService') - -// const { voidConfig } = configState -// const current_field = voidConfig.default['whichApi'] as VoidConfigField - -// return ( -//
- -// {/* choose the field */} -//
-// -// -//
- -//
- -// {/* render all fields, but hide the ones not visible for fast tab switching */} -// {nonDefaultConfigFields.map(field => { -// return
-// {Object.keys(configStateService.voidConfigInfo[field]).map((param) => ( -// -// ))} -//
-// })} -//
-// ) -// } - diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index cc71ac3b..3c3e00d3 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -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'; diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx new file mode 100644 index 00000000..a40709f5 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx @@ -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 <> + + { + 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} + /> + + +} + + +const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => { + const voidConfigState = useConfigState() + const { models, ...others } = voidConfigState[providerName] + return <> +

{titleOfProviderName(providerName)}

+ {/* settings besides models (e.g. api key) */} + {Object.keys(others).map((settingName, i) => { + return + })} + +} + + +export const VoidProviderSettings = () => { + + return <> + {providerNames.map(providerName => + + )} + + + +} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx index 0bbc7f1d..ad4e5464 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx @@ -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; - multiline: boolean; - initVal: string; -}) => { - const contextViewProvider = useService('contextViewService'); - const containerRef = useRef(null); +export const WidgetComponent = ({ ctor, propsFn, dispose, onCreateInstance } + : { + ctor: { new(...params: CtorParams): Instance }, + propsFn: (container: HTMLDivElement) => CtorParams, + onCreateInstance: (instance: Instance) => IDisposable[], + dispose: (instance: Instance) => void, + } +) => { + const containerRef = useRef(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
+} + + + +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 [ + 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
; - - // return